Browse Source

Creates initial version of xmpp module.

j8
hristoterezov 10 years ago
parent
commit
e4e66a03d7
56 changed files with 8947 additions and 3386 deletions
  1. 1
    720
      app.js
  2. 0
    17
      estos_log.js
  3. 4
    12
      index.html
  4. 4
    4
      keyboard_shortcut.js
  5. 4
    3
      libs/modules/API.bundle.js
  6. 8
    15
      libs/modules/RTC.bundle.js
  7. 596
    285
      libs/modules/UI.bundle.js
  8. 3
    3
      libs/modules/connectionquality.bundle.js
  9. 3
    2
      libs/modules/desktopsharing.bundle.js
  10. 34
    72
      libs/modules/simulcast.bundle.js
  11. 13
    22
      libs/modules/statistics.bundle.js
  12. 5082
    0
      libs/modules/xmpp.bundle.js
  13. 0
    103
      libs/rayo.js
  14. 0
    327
      libs/strophe/strophe.jingle.js
  15. 0
    41
      libs/strophe/strophe.util.js
  16. 0
    56
      moderatemuc.js
  17. 2
    2
      modules/API/API.js
  18. 2
    2
      modules/RTC/DataChannels.js
  19. 2
    11
      modules/RTC/RTC.js
  20. 188
    72
      modules/UI/UI.js
  21. 4
    4
      modules/UI/audio_levels/AudioLevels.js
  22. 84
    0
      modules/UI/authentication/Authentication.js
  23. 5
    5
      modules/UI/avatar/Avatar.js
  24. 2
    3
      modules/UI/etherpad/Etherpad.js
  25. 6
    10
      modules/UI/prezi/Prezi.js
  26. 3
    2
      modules/UI/side_pannels/SidePanelToggler.js
  27. 4
    5
      modules/UI/side_pannels/chat/Chat.js
  28. 1
    1
      modules/UI/side_pannels/chat/Commands.js
  29. 13
    18
      modules/UI/side_pannels/contactlist/ContactList.js
  30. 10
    10
      modules/UI/side_pannels/settings/SettingsMenu.js
  31. 47
    45
      modules/UI/toolbars/Toolbar.js
  32. 1
    1
      modules/UI/toolbars/ToolbarToggler.js
  33. 7
    0
      modules/UI/util/UIUtil.js
  34. 194
    84
      modules/UI/videolayout/VideoLayout.js
  35. 1
    2
      modules/connectionquality/connectionquality.js
  36. 1
    1
      modules/desktopsharing/desktopsharing.js
  37. 32
    71
      modules/simulcast/SimulcastReceiver.js
  38. 3
    24
      modules/statistics/RTPStatsCollector.js
  39. 9
    8
      modules/statistics/statistics.js
  40. 238
    28
      modules/xmpp/JingleSession.js
  41. 3
    510
      modules/xmpp/SDP.js
  42. 165
    0
      modules/xmpp/SDPDiffer.js
  43. 349
    0
      modules/xmpp/SDPUtil.js
  44. 2
    0
      modules/xmpp/TraceablePeerConnection.js
  45. 73
    70
      modules/xmpp/moderator.js
  46. 152
    0
      modules/xmpp/recording.js
  47. 607
    0
      modules/xmpp/strophe.emuc.js
  48. 334
    0
      modules/xmpp/strophe.jingle.js
  49. 20
    0
      modules/xmpp/strophe.logger.js
  50. 58
    0
      modules/xmpp/strophe.moderate.js
  51. 95
    0
      modules/xmpp/strophe.rayo.js
  52. 42
    0
      modules/xmpp/strophe.util.js
  53. 422
    0
      modules/xmpp/xmpp.js
  54. 0
    548
      muc.js
  55. 0
    167
      recording.js
  56. 14
    0
      service/xmpp/XMPPEvents.js

+ 1
- 720
app.js View File

1
 /* jshint -W117 */
1
 /* jshint -W117 */
2
 /* application specific logic */
2
 /* application specific logic */
3
-var connection = null;
4
-var authenticatedUser = false;
5
-/* Initial "authentication required" dialog */
6
-var authDialog = null;
7
-/* Loop retry ID that wits for other user to create the room */
8
-var authRetryId = null;
9
-var activecall = null;
10
 var nickname = null;
3
 var nickname = null;
11
 var focusMucJid = null;
4
 var focusMucJid = null;
12
-var roomName = null;
13
 var ssrc2jid = {};
5
 var ssrc2jid = {};
14
-var bridgeIsDown = false;
15
 //TODO: this array must be removed when firefox implement multistream support
6
 //TODO: this array must be removed when firefox implement multistream support
16
 var notReceivedSSRCs = [];
7
 var notReceivedSSRCs = [];
17
 
8
 
27
  * @type {String}
18
  * @type {String}
28
  */
19
  */
29
 var focusedVideoInfo = null;
20
 var focusedVideoInfo = null;
30
-var mutedAudios = {};
31
-/**
32
- * Remembers if we were muted by the focus.
33
- * @type {boolean}
34
- */
35
-var forceMuted = false;
36
-/**
37
- * Indicates if we have muted our audio before the conference has started.
38
- * @type {boolean}
39
- */
40
-var preMuted = false;
41
-
42
-var localVideoSrc = null;
43
-var flipXLocalVideo = true;
44
-var isFullScreen = false;
45
-var currentVideoWidth = null;
46
-var currentVideoHeight = null;
47
-
48
-var sessionTerminated = false;
49
 
21
 
50
 function init() {
22
 function init() {
51
 
23
 
52
-
53
-    RTC.addStreamListener(maybeDoJoin, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
54
     RTC.start();
24
     RTC.start();
25
+    xmpp.start(UI.getCreadentials);
55
 
26
 
56
-    var jid = document.getElementById('jid').value || config.hosts.anonymousdomain || config.hosts.domain || window.location.hostname;
57
-    connect(jid);
58
-}
59
-
60
-function connect(jid, password) {
61
-    connection = new Strophe.Connection(document.getElementById('boshURL').value || config.bosh || '/http-bind');
62
-
63
-    var settings = UI.getSettings();
64
-    var email = settings.email;
65
-    var displayName = settings.displayName;
66
-    if(email) {
67
-        connection.emuc.addEmailToPresence(email);
68
-    } else {
69
-        connection.emuc.addUserIdToPresence(settings.uid);
70
-    }
71
-    if(displayName) {
72
-        connection.emuc.addDisplayNameToPresence(displayName);
73
-    }
74
-
75
-    if (connection.disco) {
76
-        // for chrome, add multistream cap
77
-    }
78
-    connection.jingle.pc_constraints = RTC.getPCConstraints();
79
-    if (config.useIPv6) {
80
-        // https://code.google.com/p/webrtc/issues/detail?id=2828
81
-        if (!connection.jingle.pc_constraints.optional) connection.jingle.pc_constraints.optional = [];
82
-        connection.jingle.pc_constraints.optional.push({googIPv6: true});
83
-    }
84
-
85
-    if(!password)
86
-        password = document.getElementById('password').value;
87
-
88
-    var anonymousConnectionFailed = false;
89
-    connection.connect(jid, password, function (status, msg) {
90
-        console.log('Strophe status changed to', Strophe.getStatusString(status));
91
-        if (status === Strophe.Status.CONNECTED) {
92
-            if (config.useStunTurn) {
93
-                connection.jingle.getStunAndTurnCredentials();
94
-            }
95
-            document.getElementById('connect').disabled = true;
96
-
97
-            console.info("My Jabber ID: " + connection.jid);
98
-
99
-            if(password)
100
-                authenticatedUser = true;
101
-            maybeDoJoin();
102
-        } else if (status === Strophe.Status.CONNFAIL) {
103
-            if(msg === 'x-strophe-bad-non-anon-jid') {
104
-                anonymousConnectionFailed = true;
105
-            }
106
-        } else if (status === Strophe.Status.DISCONNECTED) {
107
-            if(anonymousConnectionFailed) {
108
-                // prompt user for username and password
109
-                $(document).trigger('passwordrequired.main');
110
-            }
111
-        } else if (status === Strophe.Status.AUTHFAIL) {
112
-            // wrong password or username, prompt user
113
-            $(document).trigger('passwordrequired.main');
114
-
115
-        }
116
-    });
117
-}
118
-
119
-
120
-
121
-function maybeDoJoin() {
122
-    if (connection && connection.connected && Strophe.getResourceFromJid(connection.jid) // .connected is true while connecting?
123
-        && (RTC.localAudio || RTC.localVideo)) {
124
-        doJoin();
125
-    }
126
-}
127
-
128
-function doJoin() {
129
-    if (!roomName) {
130
-        UI.generateRoomName();
131
-    }
132
-
133
-    Moderator.allocateConferenceFocus(
134
-        roomName, doJoinAfterFocus);
135
-}
136
-
137
-function doJoinAfterFocus() {
138
-
139
-    // Close authentication dialog if opened
140
-    if (authDialog) {
141
-        UI.messageHandler.closeDialog();
142
-        authDialog = null;
143
-    }
144
-    // Clear retry interval, so that we don't call 'doJoinAfterFocus' twice
145
-    if (authRetryId) {
146
-        window.clearTimeout(authRetryId);
147
-        authRetryId = null;
148
-    }
149
-
150
-    var roomjid;
151
-    roomjid = roomName;
152
-
153
-    if (config.useNicks) {
154
-        var nick = window.prompt('Your nickname (optional)');
155
-        if (nick) {
156
-            roomjid += '/' + nick;
157
-        } else {
158
-            roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
159
-        }
160
-    } else {
161
-
162
-        var tmpJid = Strophe.getNodeFromJid(connection.jid);
163
-
164
-        if(!authenticatedUser)
165
-            tmpJid = tmpJid.substr(0, 8);
166
-
167
-        roomjid += '/' + tmpJid;
168
-    }
169
-    connection.emuc.doJoin(roomjid);
170
-}
171
-
172
-function waitForRemoteVideo(selector, ssrc, stream, jid) {
173
-    // XXX(gp) so, every call to this function is *always* preceded by a call
174
-    // to the RTC.attachMediaStream() function but that call is *not* followed
175
-    // by an update to the videoSrcToSsrc map!
176
-    //
177
-    // The above way of doing things results in video SRCs that don't correspond
178
-    // to any SSRC for a short period of time (to be more precise, for as long
179
-    // the waitForRemoteVideo takes to complete). This causes problems (see
180
-    // bellow).
181
-    //
182
-    // I'm wondering why we need to do that; i.e. why call RTC.attachMediaStream()
183
-    // a second time in here and only then update the videoSrcToSsrc map? Why
184
-    // not simply update the videoSrcToSsrc map when the RTC.attachMediaStream()
185
-    // is called the first time? I actually do that in the lastN changed event
186
-    // handler because the "orphan" video SRC is causing troubles there. The
187
-    // purpose of this method would then be to fire the "videoactive.jingle".
188
-    //
189
-    // Food for though I guess :-)
190
-
191
-    if (selector.removed || !selector.parent().is(":visible")) {
192
-        console.warn("Media removed before had started", selector);
193
-        return;
194
-    }
195
-
196
-    if (stream.id === 'mixedmslabel') return;
197
-
198
-    if (selector[0].currentTime > 0) {
199
-        var videoStream = simulcast.getReceivingVideoStream(stream);
200
-        RTC.attachMediaStream(selector, videoStream); // FIXME: why do i have to do this for FF?
201
-
202
-        // FIXME: add a class that will associate peer Jid, video.src, it's ssrc and video type
203
-        //        in order to get rid of too many maps
204
-        if (ssrc && jid) {
205
-            jid2Ssrc[Strophe.getResourceFromJid(jid)] = ssrc;
206
-        } else {
207
-            console.warn("No ssrc given for jid", jid);
208
-        }
209
-
210
-        $(document).trigger('videoactive.jingle', [selector]);
211
-    } else {
212
-        setTimeout(function () {
213
-            waitForRemoteVideo(selector, ssrc, stream, jid);
214
-            }, 250);
215
-    }
216
-}
217
-
218
-$(document).bind('remotestreamadded.jingle', function (event, data, sid) {
219
-    waitForPresence(data, sid);
220
-});
221
-
222
-function waitForPresence(data, sid) {
223
-    var sess = connection.jingle.sessions[sid];
224
-
225
-    var thessrc;
226
-
227
-    // look up an associated JID for a stream id
228
-    if (data.stream.id && data.stream.id.indexOf('mixedmslabel') === -1) {
229
-        // look only at a=ssrc: and _not_ at a=ssrc-group: lines
230
-
231
-        var ssrclines
232
-            = SDPUtil.find_lines(sess.peerconnection.remoteDescription.sdp, 'a=ssrc:');
233
-        ssrclines = ssrclines.filter(function (line) {
234
-            // NOTE(gp) previously we filtered on the mslabel, but that property
235
-            // is not always present.
236
-            // return line.indexOf('mslabel:' + data.stream.label) !== -1;
237
-
238
-            return ((line.indexOf('msid:' + data.stream.id) !== -1));
239
-        });
240
-        if (ssrclines.length) {
241
-            thessrc = ssrclines[0].substring(7).split(' ')[0];
242
-
243
-            // We signal our streams (through Jingle to the focus) before we set
244
-            // our presence (through which peers associate remote streams to
245
-            // jids). So, it might arrive that a remote stream is added but
246
-            // ssrc2jid is not yet updated and thus data.peerjid cannot be
247
-            // successfully set. Here we wait for up to a second for the
248
-            // presence to arrive.
249
-
250
-            if (!ssrc2jid[thessrc]) {
251
-                // TODO(gp) limit wait duration to 1 sec.
252
-                setTimeout(function(d, s) {
253
-                    return function() {
254
-                            waitForPresence(d, s);
255
-                    }
256
-                }(data, sid), 250);
257
-                return;
258
-            }
259
-
260
-            // ok to overwrite the one from focus? might save work in colibri.js
261
-            console.log('associated jid', ssrc2jid[thessrc], data.peerjid);
262
-            if (ssrc2jid[thessrc]) {
263
-                data.peerjid = ssrc2jid[thessrc];
264
-            }
265
-        }
266
-    }
267
-
268
-    //TODO: this code should be removed when firefox implement multistream support
269
-    if(RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_FIREFOX)
270
-    {
271
-        if((notReceivedSSRCs.length == 0) ||
272
-            !ssrc2jid[notReceivedSSRCs[notReceivedSSRCs.length - 1]])
273
-        {
274
-            // TODO(gp) limit wait duration to 1 sec.
275
-            setTimeout(function(d, s) {
276
-                return function() {
277
-                    waitForPresence(d, s);
278
-                }
279
-            }(data, sid), 250);
280
-            return;
281
-        }
282
-
283
-        thessrc = notReceivedSSRCs.pop();
284
-        if (ssrc2jid[thessrc]) {
285
-            data.peerjid = ssrc2jid[thessrc];
286
-        }
287
-    }
288
-
289
-    RTC.createRemoteStream(data, sid, thessrc);
290
-
291
-    var isVideo = data.stream.getVideoTracks().length > 0;
292
-    // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
293
-    if (isVideo &&
294
-        data.peerjid && sess.peerjid === data.peerjid &&
295
-        data.stream.getVideoTracks().length === 0 &&
296
-        RTC.localVideo.getTracks().length > 0) {
297
-        //
298
-        window.setTimeout(function () {
299
-            sendKeyframe(sess.peerconnection);
300
-        }, 3000);
301
-    }
302
-}
303
-
304
-// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
305
-function sendKeyframe(pc) {
306
-    console.log('sendkeyframe', pc.iceConnectionState);
307
-    if (pc.iceConnectionState !== 'connected') return; // safe...
308
-    pc.setRemoteDescription(
309
-        pc.remoteDescription,
310
-        function () {
311
-            pc.createAnswer(
312
-                function (modifiedAnswer) {
313
-                    pc.setLocalDescription(
314
-                        modifiedAnswer,
315
-                        function () {
316
-                            // noop
317
-                        },
318
-                        function (error) {
319
-                            console.log('triggerKeyframe setLocalDescription failed', error);
320
-                            UI.messageHandler.showError();
321
-                        }
322
-                    );
323
-                },
324
-                function (error) {
325
-                    console.log('triggerKeyframe createAnswer failed', error);
326
-                    UI.messageHandler.showError();
327
-                }
328
-            );
329
-        },
330
-        function (error) {
331
-            console.log('triggerKeyframe setRemoteDescription failed', error);
332
-            UI.messageHandler.showError();
333
-        }
334
-    );
335
-}
336
-
337
-// Really mute video, i.e. dont even send black frames
338
-function muteVideo(pc, unmute) {
339
-    // FIXME: this probably needs another of those lovely state safeguards...
340
-    // which checks for iceconn == connected and sigstate == stable
341
-    pc.setRemoteDescription(pc.remoteDescription,
342
-        function () {
343
-            pc.createAnswer(
344
-                function (answer) {
345
-                    var sdp = new SDP(answer.sdp);
346
-                    if (sdp.media.length > 1) {
347
-                        if (unmute)
348
-                            sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
349
-                        else
350
-                            sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
351
-                        sdp.raw = sdp.session + sdp.media.join('');
352
-                        answer.sdp = sdp.raw;
353
-                    }
354
-                    pc.setLocalDescription(answer,
355
-                        function () {
356
-                            console.log('mute SLD ok');
357
-                        },
358
-                        function (error) {
359
-                            console.log('mute SLD error');
360
-                            UI.messageHandler.showError('Error',
361
-                                'Oops! Something went wrong and we failed to ' +
362
-                                    'mute! (SLD Failure)');
363
-                        }
364
-                    );
365
-                },
366
-                function (error) {
367
-                    console.log(error);
368
-                    UI.messageHandler.showError();
369
-                }
370
-            );
371
-        },
372
-        function (error) {
373
-            console.log('muteVideo SRD error');
374
-            UI.messageHandler.showError('Error',
375
-                'Oops! Something went wrong and we failed to stop video!' +
376
-                    '(SRD Failure)');
377
-
378
-        }
379
-    );
380
-}
381
-
382
-$(document).bind('setLocalDescription.jingle', function (event, sid) {
383
-    // put our ssrcs into presence so other clients can identify our stream
384
-    var sess = connection.jingle.sessions[sid];
385
-    var newssrcs = [];
386
-    var media = simulcast.parseMedia(sess.peerconnection.localDescription);
387
-    media.forEach(function (media) {
388
-
389
-        if(Object.keys(media.sources).length > 0) {
390
-            // TODO(gp) maybe exclude FID streams?
391
-            Object.keys(media.sources).forEach(function (ssrc) {
392
-                newssrcs.push({
393
-                    'ssrc': ssrc,
394
-                    'type': media.type,
395
-                    'direction': media.direction
396
-                });
397
-            });
398
-        }
399
-        else if(sess.localStreamsSSRC && sess.localStreamsSSRC[media.type])
400
-        {
401
-            newssrcs.push({
402
-                'ssrc': sess.localStreamsSSRC[media.type],
403
-                'type': media.type,
404
-                'direction': media.direction
405
-            });
406
-        }
407
-
408
-    });
409
-
410
-    console.log('new ssrcs', newssrcs);
411
-
412
-    // Have to clear presence map to get rid of removed streams
413
-    connection.emuc.clearPresenceMedia();
414
-
415
-    if (newssrcs.length > 0) {
416
-        for (var i = 1; i <= newssrcs.length; i ++) {
417
-            // Change video type to screen
418
-            if (newssrcs[i-1].type === 'video' && desktopsharing.isUsingScreenStream()) {
419
-                newssrcs[i-1].type = 'screen';
420
-            }
421
-            connection.emuc.addMediaToPresence(i,
422
-                newssrcs[i-1].type, newssrcs[i-1].ssrc, newssrcs[i-1].direction);
423
-        }
424
-
425
-        connection.emuc.sendPresence();
426
-    }
427
-});
428
-
429
-$(document).bind('iceconnectionstatechange.jingle', function (event, sid, session) {
430
-    switch (session.peerconnection.iceConnectionState) {
431
-    case 'checking': 
432
-        session.timeChecking = (new Date()).getTime();
433
-        session.firstconnect = true;
434
-        break;
435
-    case 'completed': // on caller side
436
-    case 'connected':
437
-        if (session.firstconnect) {
438
-            session.firstconnect = false;
439
-            var metadata = {};
440
-            metadata.setupTime = (new Date()).getTime() - session.timeChecking;
441
-            session.peerconnection.getStats(function (res) {
442
-                if(res && res.result) {
443
-                    res.result().forEach(function (report) {
444
-                        if (report.type == 'googCandidatePair' && report.stat('googActiveConnection') == 'true') {
445
-                            metadata.localCandidateType = report.stat('googLocalCandidateType');
446
-                            metadata.remoteCandidateType = report.stat('googRemoteCandidateType');
447
-
448
-                            // log pair as well so we can get nice pie charts
449
-                            metadata.candidatePair = report.stat('googLocalCandidateType') + ';' + report.stat('googRemoteCandidateType');
450
-
451
-                            if (report.stat('googRemoteAddress').indexOf('[') === 0) {
452
-                                metadata.ipv6 = true;
453
-                            }
454
-                        }
455
-                    });
456
-                }
457
-            });
458
-        }
459
-        break;
460
-    }
461
-});
462
-
463
-$(document).bind('presence.muc', function (event, jid, info, pres) {
464
-
465
-    //check if the video bridge is available
466
-    if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
467
-        bridgeIsDown = true;
468
-        UI.messageHandler.showError("Error",
469
-            "Jitsi Videobridge is currently unavailable. Please try again later!");
470
-    }
471
-
472
-    if (info.isFocus)
473
-    {
474
-        return;
475
-    }
476
-
477
-    // Remove old ssrcs coming from the jid
478
-    Object.keys(ssrc2jid).forEach(function (ssrc) {
479
-        if (ssrc2jid[ssrc] == jid) {
480
-            delete ssrc2jid[ssrc];
481
-            delete ssrc2videoType[ssrc];
482
-        }
483
-    });
484
-
485
-    $(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
486
-        //console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
487
-        var ssrcV = ssrc.getAttribute('ssrc');
488
-        ssrc2jid[ssrcV] = jid;
489
-        notReceivedSSRCs.push(ssrcV);
490
-
491
-        var type = ssrc.getAttribute('type');
492
-        ssrc2videoType[ssrcV] = type;
493
-
494
-        // might need to update the direction if participant just went from sendrecv to recvonly
495
-        if (type === 'video' || type === 'screen') {
496
-            var el = $('#participant_'  + Strophe.getResourceFromJid(jid) + '>video');
497
-            switch (ssrc.getAttribute('direction')) {
498
-            case 'sendrecv':
499
-                el.show();
500
-                break;
501
-            case 'recvonly':
502
-                el.hide();
503
-                // FIXME: Check if we have to change large video
504
-                //VideoLayout.updateLargeVideo(el);
505
-                break;
506
-            }
507
-        }
508
-    });
509
-
510
-    var displayName = !config.displayJids
511
-        ? info.displayName : Strophe.getResourceFromJid(jid);
512
-
513
-    if (displayName && displayName.length > 0)
514
-        $(document).trigger('displaynamechanged',
515
-                            [jid, displayName]);
516
-    /*if (focus !== null && info.displayName !== null) {
517
-        focus.setEndpointDisplayName(jid, info.displayName);
518
-    }*/
519
-
520
-    //check if the video bridge is available
521
-    if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
522
-        bridgeIsDown = true;
523
-        UI.messageHandler.showError("Error",
524
-            "Jitsi Videobridge is currently unavailable. Please try again later!");
525
-    }
526
-
527
-    var id = $(pres).find('>userID').text();
528
-    var email = $(pres).find('>email');
529
-    if(email.length > 0) {
530
-        id = email.text();
531
-    }
532
-    UI.setUserAvatar(jid, id);
533
-
534
-});
535
-
536
-$(document).bind('kicked.muc', function (event, jid) {
537
-    console.info(jid + " has been kicked from MUC!");
538
-    if (connection.emuc.myroomjid === jid) {
539
-        sessionTerminated = true;
540
-        disposeConference(false);
541
-        connection.emuc.doLeave();
542
-        UI.messageHandler.openMessageDialog("Session Terminated",
543
-            "Ouch! You have been kicked out of the meet!");
544
-    }
545
-});
546
-
547
-$(document).bind('passwordrequired.main', function (event) {
548
-    console.log('password is required');
549
-
550
-    UI.messageHandler.openTwoButtonDialog(null,
551
-        '<h2>Password required</h2>' +
552
-            '<input id="passwordrequired.username" type="text" placeholder="user@domain.net" autofocus>' +
553
-            '<input id="passwordrequired.password" type="password" placeholder="user password">',
554
-        true,
555
-        "Ok",
556
-        function (e, v, m, f) {
557
-            if (v) {
558
-                var username = document.getElementById('passwordrequired.username');
559
-                var password = document.getElementById('passwordrequired.password');
560
-
561
-                if (username.value !== null && password.value != null) {
562
-                    connect(username.value, password.value);
563
-                }
564
-            }
565
-        },
566
-        function (event) {
567
-            document.getElementById('passwordrequired.username').focus();
568
-        }
569
-    );
570
-});
571
-
572
-/**
573
- * Checks if video identified by given src is desktop stream.
574
- * @param videoSrc eg.
575
- * blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395
576
- * @returns {boolean}
577
- */
578
-function isVideoSrcDesktop(jid) {
579
-    // FIXME: fix this mapping mess...
580
-    // figure out if large video is desktop stream or just a camera
581
-
582
-    if(!jid)
583
-        return false;
584
-    var isDesktop = false;
585
-    if (connection.emuc.myroomjid &&
586
-        Strophe.getResourceFromJid(connection.emuc.myroomjid) === jid) {
587
-        // local video
588
-        isDesktop = desktopsharing.isUsingScreenStream();
589
-    } else {
590
-        // Do we have associations...
591
-        var videoSsrc = jid2Ssrc[jid];
592
-        if (videoSsrc) {
593
-            var videoType = ssrc2videoType[videoSsrc];
594
-            if (videoType) {
595
-                // Finally there...
596
-                isDesktop = videoType === 'screen';
597
-            } else {
598
-                console.error("No video type for ssrc: " + videoSsrc);
599
-            }
600
-        } else {
601
-            console.error("No ssrc for jid: " + jid);
602
-        }
603
-    }
604
-    return isDesktop;
605
-}
606
-
607
-/**
608
- * Mutes/unmutes the local video.
609
- *
610
- * @param mute <tt>true</tt> to mute the local video; otherwise, <tt>false</tt>
611
- * @param options an object which specifies optional arguments such as the
612
- * <tt>boolean</tt> key <tt>byUser</tt> with default value <tt>true</tt> which
613
- * specifies whether the method was initiated in response to a user command (in
614
- * contrast to an automatic decision taken by the application logic)
615
- */
616
-function setVideoMute(mute, options) {
617
-    if (connection && RTC.localVideo) {
618
-        if (activecall) {
619
-            activecall.setVideoMute(
620
-                mute,
621
-                function (mute) {
622
-                    var video = $('#video');
623
-                    var communicativeClass = "icon-camera";
624
-                    var muteClass = "icon-camera icon-camera-disabled";
625
-
626
-                    if (mute) {
627
-                        video.removeClass(communicativeClass);
628
-                        video.addClass(muteClass);
629
-                    } else {
630
-                        video.removeClass(muteClass);
631
-                        video.addClass(communicativeClass);
632
-                    }
633
-                    connection.emuc.addVideoInfoToPresence(mute);
634
-                    connection.emuc.sendPresence();
635
-                },
636
-                options);
637
-        }
638
-    }
639
-}
640
-
641
-$(document).on('inlastnchanged', function (event, oldValue, newValue) {
642
-    if (config.muteLocalVideoIfNotInLastN) {
643
-        setVideoMute(!newValue, { 'byUser': false });
644
-    }
645
-});
646
-
647
-/**
648
- * Mutes/unmutes the local video.
649
- */
650
-function toggleVideo() {
651
-    buttonClick("#video", "icon-camera icon-camera-disabled");
652
-
653
-    if (connection && activecall && RTC.localVideo ) {
654
-        setVideoMute(!RTC.localVideo.isMuted());
655
-    }
656
-}
657
-
658
-/**
659
- * Mutes / unmutes audio for the local participant.
660
- */
661
-function toggleAudio() {
662
-    setAudioMuted(!RTC.localAudio.isMuted());
663
-}
664
-
665
-/**
666
- * Sets muted audio state for the local participant.
667
- */
668
-function setAudioMuted(mute) {
669
-    if (!(connection && RTC.localAudio)) {
670
-        preMuted = mute;
671
-        // We still click the button.
672
-        buttonClick("#mute", "icon-microphone icon-mic-disabled");
673
-        return;
674
-    }
675
-
676
-    if (forceMuted && !mute) {
677
-        console.info("Asking focus for unmute");
678
-        connection.moderate.setMute(connection.emuc.myroomjid, mute);
679
-        // FIXME: wait for result before resetting muted status
680
-        forceMuted = false;
681
-    }
682
-
683
-    if (mute == RTC.localAudio.isMuted()) {
684
-        // Nothing to do
685
-        return;
686
-    }
687
-
688
-    // It is not clear what is the right way to handle multiple tracks.
689
-    // So at least make sure that they are all muted or all unmuted and
690
-    // that we send presence just once.
691
-    RTC.localAudio.mute();
692
-    // isMuted is the opposite of audioEnabled
693
-    connection.emuc.addAudioInfoToPresence(mute);
694
-    connection.emuc.sendPresence();
695
-    UI.showLocalAudioIndicator(mute);
696
-
697
-    buttonClick("#mute", "icon-microphone icon-mic-disabled");
698
 }
27
 }
699
 
28
 
700
 
29
 
706
     UI.start();
35
     UI.start();
707
     statistics.start();
36
     statistics.start();
708
     
37
     
709
-    Moderator.init();
710
-
711
     // Set default desktop sharing method
38
     // Set default desktop sharing method
712
     desktopsharing.init();
39
     desktopsharing.init();
713
 });
40
 });
714
 
41
 
715
 $(window).bind('beforeunload', function () {
42
 $(window).bind('beforeunload', function () {
716
-    if (connection && connection.connected) {
717
-        // ensure signout
718
-        $.ajax({
719
-            type: 'POST',
720
-            url: config.bosh,
721
-            async: false,
722
-            cache: false,
723
-            contentType: 'application/xml',
724
-            data: "<body rid='" + (connection.rid || connection._proto.rid)
725
-                + "' xmlns='http://jabber.org/protocol/httpbind' sid='"
726
-                + (connection.sid || connection._proto.sid)
727
-                + "' type='terminate'><presence xmlns='jabber:client' type='unavailable'/></body>",
728
-            success: function (data) {
729
-                console.log('signed out');
730
-                console.log(data);
731
-            },
732
-            error: function (XMLHttpRequest, textStatus, errorThrown) {
733
-                console.log('signout error', textStatus + ' (' + errorThrown + ')');
734
-            }
735
-        });
736
-    }
737
-    disposeConference(true);
738
     if(API.isEnabled())
43
     if(API.isEnabled())
739
         API.dispose();
44
         API.dispose();
740
 });
45
 });
741
 
46
 
742
-function disposeConference(onUnload) {
743
-    UI.onDisposeConference(onUnload);
744
-    var handler = activecall;
745
-    if (handler && handler.peerconnection) {
746
-        // FIXME: probably removing streams is not required and close() should
747
-        // be enough
748
-        if (RTC.localAudio) {
749
-            handler.peerconnection.removeStream(RTC.localAudio.getOriginalStream(), onUnload);
750
-        }
751
-        if (RTC.localVideo) {
752
-            handler.peerconnection.removeStream(RTC.localVideo.getOriginalStream(), onUnload);
753
-        }
754
-        handler.peerconnection.close();
755
-    }
756
-    statistics.onDisposeConference(onUnload);
757
-    activecall = null;
758
-}
759
-
760
-/**
761
- * Changes the style class of the element given by id.
762
- */
763
-function buttonClick(id, classname) {
764
-    $(id).toggleClass(classname); // add the class to the clicked element
765
-}

+ 0
- 17
estos_log.js View File

1
-/* global Strophe */
2
-Strophe.addConnectionPlugin('logger', {
3
-    // logs raw stanzas and makes them available for download as JSON
4
-    connection: null,
5
-    log: [],
6
-    init: function (conn) {
7
-        this.connection = conn;
8
-        this.connection.rawInput = this.log_incoming.bind(this);
9
-        this.connection.rawOutput = this.log_outgoing.bind(this);
10
-    },
11
-    log_incoming: function (stanza) {
12
-        this.log.push([new Date().getTime(), 'incoming', stanza]);
13
-    },
14
-    log_outgoing: function (stanza) {
15
-        this.log.push([new Date().getTime(), 'outgoing', stanza]);
16
-    },
17
-});

+ 4
- 12
index.html View File

11
     <meta itemprop="image" content="/images/jitsilogo.png"/>
11
     <meta itemprop="image" content="/images/jitsilogo.png"/>
12
     <script src="libs/jquery-2.1.1.min.js"></script>
12
     <script src="libs/jquery-2.1.1.min.js"></script>
13
     <script src="config.js?v=5"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
13
     <script src="config.js?v=5"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
14
-    <script src="libs/strophe/strophe.jingle.adapter.js?v=4"></script><!-- strophe.jingle bundles -->
15
     <script src="libs/strophe/strophe.min.js?v=1"></script>
14
     <script src="libs/strophe/strophe.min.js?v=1"></script>
16
     <script src="libs/strophe/strophe.disco.min.js?v=1"></script>
15
     <script src="libs/strophe/strophe.disco.min.js?v=1"></script>
17
     <script src="libs/strophe/strophe.caps.jsonly.min.js?v=1"></script>
16
     <script src="libs/strophe/strophe.caps.jsonly.min.js?v=1"></script>
18
-    <script src="libs/strophe/strophe.jingle.js?v=3"></script>
19
-    <script src="libs/strophe/strophe.jingle.sdp.js?v=5"></script>
20
-    <script src="libs/strophe/strophe.jingle.session.js?v=6"></script>
21
-    <script src="libs/strophe/strophe.util.js"></script>
22
     <script src="libs/jquery-ui.js"></script>
17
     <script src="libs/jquery-ui.js"></script>
23
-    <script src="libs/rayo.js?v=1"></script>
24
     <script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
18
     <script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
25
     <script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
19
     <script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
26
     <script src="libs/pako.bundle.js?v=1"></script><!-- zlib deflate -->
20
     <script src="libs/pako.bundle.js?v=1"></script><!-- zlib deflate -->
29
     <script src="service/RTC/RTCBrowserType.js?v=1"></script>
23
     <script src="service/RTC/RTCBrowserType.js?v=1"></script>
30
     <script src="service/RTC/StreamEventTypes.js?v=2"></script>
24
     <script src="service/RTC/StreamEventTypes.js?v=2"></script>
31
     <script src="service/RTC/MediaStreamTypes.js?v=1"></script>
25
     <script src="service/RTC/MediaStreamTypes.js?v=1"></script>
26
+    <script src="service/xmpp/XMPPEvents.js?v=1"></script>
32
     <script src="service/desktopsharing/DesktopSharingEventTypes.js?v=1"></script>
27
     <script src="service/desktopsharing/DesktopSharingEventTypes.js?v=1"></script>
33
     <script src="libs/modules/simulcast.bundle.js?v=3"></script>
28
     <script src="libs/modules/simulcast.bundle.js?v=3"></script>
34
     <script src="libs/modules/connectionquality.bundle.js?v=1"></script>
29
     <script src="libs/modules/connectionquality.bundle.js?v=1"></script>
35
     <script src="libs/modules/UI.bundle.js?v=5"></script>
30
     <script src="libs/modules/UI.bundle.js?v=5"></script>
36
     <script src="libs/modules/statistics.bundle.js?v=1"></script>
31
     <script src="libs/modules/statistics.bundle.js?v=1"></script>
37
     <script src="libs/modules/RTC.bundle.js?v=4"></script>
32
     <script src="libs/modules/RTC.bundle.js?v=4"></script>
38
-    <script src="muc.js?v=17"></script><!-- simple MUC library -->
39
-    <script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
40
     <script src="libs/modules/desktopsharing.bundle.js?v=3"></script><!-- desktop sharing -->
33
     <script src="libs/modules/desktopsharing.bundle.js?v=3"></script><!-- desktop sharing -->
34
+    <script src="util.js?v=7"></script><!-- utility functions -->
35
+    <script src="libs/modules/xmpp.bundle.js?v=1"></script>
41
     <script src="app.js?v=26"></script><!-- application logic -->
36
     <script src="app.js?v=26"></script><!-- application logic -->
42
     <script src="libs/modules/API.bundle.js?v=1"></script>
37
     <script src="libs/modules/API.bundle.js?v=1"></script>
43
-    <script src="util.js?v=7"></script><!-- utility functions -->
44
-    <script src="moderatemuc.js?v=4"></script><!-- moderator plugin -->
38
+
45
     <script src="analytics.js?v=1"></script><!-- google analytics plugin -->
39
     <script src="analytics.js?v=1"></script><!-- google analytics plugin -->
46
-    <script src="moderator.js?v=2"></script><!-- media stream -->
47
-    <script src="tracking.js?v=1"></script><!-- tracking -->
48
     <script src="keyboard_shortcut.js?v=4"></script>
40
     <script src="keyboard_shortcut.js?v=4"></script>
49
     <link rel="stylesheet" href="css/font.css?v=6"/>
41
     <link rel="stylesheet" href="css/font.css?v=6"/>
50
     <link rel="stylesheet" href="css/toastr.css?v=1">
42
     <link rel="stylesheet" href="css/toastr.css?v=1">

+ 4
- 4
keyboard_shortcut.js View File

14
         77: {
14
         77: {
15
             character: "M",
15
             character: "M",
16
             id: "mutePopover",
16
             id: "mutePopover",
17
-            function: toggleAudio
17
+            function: UI.toggleAudio
18
         },
18
         },
19
         84: {
19
         84: {
20
             character: "T",
20
             character: "T",
21
             function: function() {
21
             function: function() {
22
                 if(!RTC.localAudio.isMuted()) {
22
                 if(!RTC.localAudio.isMuted()) {
23
-                    toggleAudio();
23
+                    UI.toggleAudio();
24
                 }
24
                 }
25
             }
25
             }
26
         },
26
         },
27
         86: {
27
         86: {
28
             character: "V",
28
             character: "V",
29
             id: "toggleVideoPopover",
29
             id: "toggleVideoPopover",
30
-            function: toggleVideo
30
+            function: UI.toggleVideo
31
         }
31
         }
32
     };
32
     };
33
 
33
 
53
         if(!($(":focus").is("input[type=text]") || $(":focus").is("input[type=password]") || $(":focus").is("textarea"))) {
53
         if(!($(":focus").is("input[type=text]") || $(":focus").is("input[type=password]") || $(":focus").is("textarea"))) {
54
             if(e.which === "T".charCodeAt(0)) {
54
             if(e.which === "T".charCodeAt(0)) {
55
                 if(RTC.localAudio.isMuted()) {
55
                 if(RTC.localAudio.isMuted()) {
56
-                    toggleAudio();
56
+                    UI.toggleAudio();
57
                 }
57
                 }
58
             }
58
             }
59
         }
59
         }

+ 4
- 3
libs/modules/API.bundle.js
File diff suppressed because it is too large
View File


+ 8
- 15
libs/modules/RTC.bundle.js
File diff suppressed because it is too large
View File


+ 596
- 285
libs/modules/UI.bundle.js
File diff suppressed because it is too large
View File


+ 3
- 3
libs/modules/connectionquality.bundle.js
File diff suppressed because it is too large
View File


+ 3
- 2
libs/modules/desktopsharing.bundle.js
File diff suppressed because it is too large
View File


+ 34
- 72
libs/modules/simulcast.bundle.js
File diff suppressed because it is too large
View File


+ 13
- 22
libs/modules/statistics.bundle.js
File diff suppressed because it is too large
View File


+ 5082
- 0
libs/modules/xmpp.bundle.js
File diff suppressed because it is too large
View File


+ 0
- 103
libs/rayo.js View File

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, roomPass)
22
-        {
23
-            var self = this;
24
-            var req = $iq(
25
-                {
26
-                    type: 'set',
27
-                    to: focusMucJid
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
-                }).up();
41
-
42
-            if (roomPass !== null && roomPass.length) {
43
-
44
-                req.c('header',
45
-                    {
46
-                        name: 'JvbRoomPassword',
47
-                        value: roomPass
48
-                    }).up();
49
-            }
50
-
51
-            this.connection.sendIQ(
52
-                req,
53
-                function (result)
54
-                {
55
-                    console.info('Dial result ', result);
56
-
57
-                    var resource = $(result).find('ref').attr('uri');
58
-                    this.call_resource = resource.substr('xmpp:'.length);
59
-                    console.info(
60
-                        "Received call resource: " + this.call_resource);
61
-                },
62
-                function (error)
63
-                {
64
-                    console.info('Dial error ', error);
65
-                }
66
-            );
67
-        },
68
-        hang_up: function ()
69
-        {
70
-            if (!this.call_resource)
71
-            {
72
-                console.warn("No call in progress");
73
-                return;
74
-            }
75
-
76
-            var self = this;
77
-            var req = $iq(
78
-                {
79
-                    type: 'set',
80
-                    to: this.call_resource
81
-                }
82
-            );
83
-            req.c('hangup',
84
-                {
85
-                    xmlns: this.RAYO_XMLNS
86
-                });
87
-
88
-            this.connection.sendIQ(
89
-                req,
90
-                function (result)
91
-                {
92
-                    console.info('Hangup result ', result);
93
-                    self.call_resource = null;
94
-                },
95
-                function (error)
96
-                {
97
-                    console.info('Hangup error ', error);
98
-                    self.call_resource = null;
99
-                }
100
-            );
101
-        }
102
-    }
103
-);

+ 0
- 327
libs/strophe/strophe.jingle.js View File

1
-/* jshint -W117 */
2
-
3
-
4
-function CallIncomingJingle(sid) {
5
-    var sess = connection.jingle.sessions[sid];
6
-
7
-    // TODO: do we check activecall == null?
8
-    activecall = sess;
9
-
10
-    statistics.onConferenceCreated(sess);
11
-    RTC.onConferenceCreated(sess);
12
-
13
-    // TODO: check affiliation and/or role
14
-    console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
15
-    sess.usedrip = true; // not-so-naive trickle ice
16
-    sess.sendAnswer();
17
-    sess.accept();
18
-
19
-};
20
-
21
-Strophe.addConnectionPlugin('jingle', {
22
-    connection: null,
23
-    sessions: {},
24
-    jid2session: {},
25
-    ice_config: {iceServers: []},
26
-    pc_constraints: {},
27
-    media_constraints: {
28
-        mandatory: {
29
-            'OfferToReceiveAudio': true,
30
-            'OfferToReceiveVideo': true
31
-        }
32
-        // MozDontOfferDataChannel: true when this is firefox
33
-    },
34
-    init: function (conn) {
35
-        this.connection = conn;
36
-        if (this.connection.disco) {
37
-            // http://xmpp.org/extensions/xep-0167.html#support
38
-            // http://xmpp.org/extensions/xep-0176.html#support
39
-            this.connection.disco.addFeature('urn:xmpp:jingle:1');
40
-            this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
41
-            this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
42
-            this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
43
-            this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video');
44
-
45
-
46
-            // this is dealt with by SDP O/A so we don't need to annouce this
47
-            //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
48
-            //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
49
-            if (config.useRtcpMux) {
50
-                this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
51
-            }
52
-            if (config.useBundle) {
53
-                this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
54
-            }
55
-            //this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
56
-        }
57
-        this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
58
-    },
59
-    onJingle: function (iq) {
60
-        var sid = $(iq).find('jingle').attr('sid');
61
-        var action = $(iq).find('jingle').attr('action');
62
-        var fromJid = iq.getAttribute('from');
63
-        // send ack first
64
-        var ack = $iq({type: 'result',
65
-            to: fromJid,
66
-            id: iq.getAttribute('id')
67
-        });
68
-        console.log('on jingle ' + action + ' from ' + fromJid, iq);
69
-        var sess = this.sessions[sid];
70
-        if ('session-initiate' != action) {
71
-            if (sess === null) {
72
-                ack.type = 'error';
73
-                ack.c('error', {type: 'cancel'})
74
-                    .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
75
-                    .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
76
-                this.connection.send(ack);
77
-                return true;
78
-            }
79
-            // compare from to sess.peerjid (bare jid comparison for later compat with message-mode)
80
-            // local jid is not checked
81
-            if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) {
82
-                console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
83
-                ack.type = 'error';
84
-                ack.c('error', {type: 'cancel'})
85
-                    .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
86
-                    .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
87
-                this.connection.send(ack);
88
-                return true;
89
-            }
90
-        } else if (sess !== undefined) {
91
-            // existing session with same session id
92
-            // this might be out-of-order if the sess.peerjid is the same as from
93
-            ack.type = 'error';
94
-            ack.c('error', {type: 'cancel'})
95
-                .c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
96
-            console.warn('duplicate session id', sid);
97
-            this.connection.send(ack);
98
-            return true;
99
-        }
100
-        // FIXME: check for a defined action
101
-        this.connection.send(ack);
102
-        // see http://xmpp.org/extensions/xep-0166.html#concepts-session
103
-        switch (action) {
104
-            case 'session-initiate':
105
-                sess = new JingleSession($(iq).attr('to'), $(iq).find('jingle').attr('sid'), this.connection);
106
-                // configure session
107
-
108
-                sess.media_constraints = this.media_constraints;
109
-                sess.pc_constraints = this.pc_constraints;
110
-                sess.ice_config = this.ice_config;
111
-
112
-                sess.initiate(fromJid, false);
113
-                // FIXME: setRemoteDescription should only be done when this call is to be accepted
114
-                sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
115
-
116
-                this.sessions[sess.sid] = sess;
117
-                this.jid2session[sess.peerjid] = sess;
118
-
119
-                // the callback should either
120
-                // .sendAnswer and .accept
121
-                // or .sendTerminate -- not necessarily synchronus
122
-                CallIncomingJingle(sess.sid);
123
-                break;
124
-            case 'session-accept':
125
-                sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
126
-                sess.accept();
127
-                $(document).trigger('callaccepted.jingle', [sess.sid]);
128
-                break;
129
-            case 'session-terminate':
130
-                // If this is not the focus sending the terminate, we have
131
-                // nothing more to do here.
132
-                if (Object.keys(this.sessions).length < 1
133
-                    || !(this.sessions[Object.keys(this.sessions)[0]]
134
-                        instanceof JingleSession))
135
-                {
136
-                    break;
137
-                }
138
-                console.log('terminating...', sess.sid);
139
-                sess.terminate();
140
-                this.terminate(sess.sid);
141
-                if ($(iq).find('>jingle>reason').length) {
142
-                    $(document).trigger('callterminated.jingle', [
143
-                        sess.sid,
144
-                        sess.peerjid,
145
-                        $(iq).find('>jingle>reason>:first')[0].tagName,
146
-                        $(iq).find('>jingle>reason>text').text()
147
-                    ]);
148
-                } else {
149
-                    $(document).trigger('callterminated.jingle',
150
-                                        [sess.sid, sess.peerjid]);
151
-                }
152
-                break;
153
-            case 'transport-info':
154
-                sess.addIceCandidate($(iq).find('>jingle>content'));
155
-                break;
156
-            case 'session-info':
157
-                var affected;
158
-                if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
159
-                    $(document).trigger('ringing.jingle', [sess.sid]);
160
-                } else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
161
-                    affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
162
-                    $(document).trigger('mute.jingle', [sess.sid, affected]);
163
-                } else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
164
-                    affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
165
-                    $(document).trigger('unmute.jingle', [sess.sid, affected]);
166
-                }
167
-                break;
168
-            case 'addsource': // FIXME: proprietary, un-jingleish
169
-            case 'source-add': // FIXME: proprietary
170
-                sess.addSource($(iq).find('>jingle>content'), fromJid);
171
-                break;
172
-            case 'removesource': // FIXME: proprietary, un-jingleish
173
-            case 'source-remove': // FIXME: proprietary
174
-                sess.removeSource($(iq).find('>jingle>content'), fromJid);
175
-                break;
176
-            default:
177
-                console.warn('jingle action not implemented', action);
178
-                break;
179
-        }
180
-        return true;
181
-    },
182
-    initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid
183
-        var sess = new JingleSession(myjid || this.connection.jid,
184
-            Math.random().toString(36).substr(2, 12), // random string
185
-            this.connection);
186
-        // configure session
187
-
188
-        sess.media_constraints = this.media_constraints;
189
-        sess.pc_constraints = this.pc_constraints;
190
-        sess.ice_config = this.ice_config;
191
-
192
-        sess.initiate(peerjid, true);
193
-        this.sessions[sess.sid] = sess;
194
-        this.jid2session[sess.peerjid] = sess;
195
-        sess.sendOffer();
196
-        return sess;
197
-    },
198
-    terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
199
-        if (sid === null || sid === undefined) {
200
-            for (sid in this.sessions) {
201
-                if (this.sessions[sid].state != 'ended') {
202
-                    this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
203
-                    this.sessions[sid].terminate();
204
-                }
205
-                delete this.jid2session[this.sessions[sid].peerjid];
206
-                delete this.sessions[sid];
207
-            }
208
-        } else if (this.sessions.hasOwnProperty(sid)) {
209
-            if (this.sessions[sid].state != 'ended') {
210
-                this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
211
-                this.sessions[sid].terminate();
212
-            }
213
-            delete this.jid2session[this.sessions[sid].peerjid];
214
-            delete this.sessions[sid];
215
-        }
216
-    },
217
-    // Used to terminate a session when an unavailable presence is received.
218
-    terminateByJid: function (jid) {
219
-        if (this.jid2session.hasOwnProperty(jid)) {
220
-            var sess = this.jid2session[jid];
221
-            if (sess) {
222
-                sess.terminate();
223
-                console.log('peer went away silently', jid);
224
-                delete this.sessions[sess.sid];
225
-                delete this.jid2session[jid];
226
-                $(document).trigger('callterminated.jingle',
227
-                                    [sess.sid, jid], 'gone');
228
-            }
229
-        }
230
-    },
231
-    terminateRemoteByJid: function (jid, reason) {
232
-        if (this.jid2session.hasOwnProperty(jid)) {
233
-            var sess = this.jid2session[jid];
234
-            if (sess) {
235
-                sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null);
236
-                sess.terminate();
237
-                console.log('terminate peer with jid', sess.sid, jid);
238
-                delete this.sessions[sess.sid];
239
-                delete this.jid2session[jid];
240
-                $(document).trigger('callterminated.jingle',
241
-                                    [sess.sid, jid, 'kicked']);
242
-            }
243
-        }
244
-    },
245
-    getStunAndTurnCredentials: function () {
246
-        // get stun and turn configuration from server via xep-0215
247
-        // uses time-limited credentials as described in
248
-        // http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
249
-        //
250
-        // see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua
251
-        // for a prosody module which implements this
252
-        //
253
-        // currently, this doesn't work with updateIce and therefore credentials with a long
254
-        // validity have to be fetched before creating the peerconnection
255
-        // TODO: implement refresh via updateIce as described in
256
-        //      https://code.google.com/p/webrtc/issues/detail?id=1650
257
-        var self = this;
258
-        this.connection.sendIQ(
259
-            $iq({type: 'get', to: this.connection.domain})
260
-                .c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}),
261
-            function (res) {
262
-                var iceservers = [];
263
-                $(res).find('>services>service').each(function (idx, el) {
264
-                    el = $(el);
265
-                    var dict = {};
266
-                    var type = el.attr('type');
267
-                    switch (type) {
268
-                        case 'stun':
269
-                            dict.url = 'stun:' + el.attr('host');
270
-                            if (el.attr('port')) {
271
-                                dict.url += ':' + el.attr('port');
272
-                            }
273
-                            iceservers.push(dict);
274
-                            break;
275
-                        case 'turn':
276
-                        case 'turns':
277
-                            dict.url = type + ':';
278
-                            if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508
279
-                                if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) {
280
-                                    dict.url += el.attr('username') + '@';
281
-                                } else {
282
-                                    dict.username = el.attr('username'); // only works in M28
283
-                                }
284
-                            }
285
-                            dict.url += el.attr('host');
286
-                            if (el.attr('port') && el.attr('port') != '3478') {
287
-                                dict.url += ':' + el.attr('port');
288
-                            }
289
-                            if (el.attr('transport') && el.attr('transport') != 'udp') {
290
-                                dict.url += '?transport=' + el.attr('transport');
291
-                            }
292
-                            if (el.attr('password')) {
293
-                                dict.credential = el.attr('password');
294
-                            }
295
-                            iceservers.push(dict);
296
-                            break;
297
-                    }
298
-                });
299
-                self.ice_config.iceServers = iceservers;
300
-            },
301
-            function (err) {
302
-                console.warn('getting turn credentials failed', err);
303
-                console.warn('is mod_turncredentials or similar installed?');
304
-            }
305
-        );
306
-        // implement push?
307
-    },
308
-
309
-    /**
310
-     * Populates the log data
311
-     */
312
-    populateData: function () {
313
-        var data = {};
314
-        Object.keys(this.sessions).forEach(function (sid) {
315
-            var session = this.sessions[sid];
316
-            if (session.peerconnection && session.peerconnection.updateLog) {
317
-                // FIXME: should probably be a .dump call
318
-                data["jingle_" + session.sid] = {
319
-                    updateLog: session.peerconnection.updateLog,
320
-                    stats: session.peerconnection.stats,
321
-                    url: window.location.href
322
-                };
323
-            }
324
-        });
325
-        return data;
326
-    }
327
-});

+ 0
- 41
libs/strophe/strophe.util.js View File

1
-/**
2
- * Strophe logger implementation. Logs from level WARN and above.
3
- */
4
-Strophe.log = function (level, msg) {
5
-    switch(level) {
6
-        case Strophe.LogLevel.WARN:
7
-            console.warn("Strophe: "+msg);
8
-            break;
9
-        case Strophe.LogLevel.ERROR:
10
-        case Strophe.LogLevel.FATAL:
11
-            console.error("Strophe: "+msg);
12
-            break;
13
-    }
14
-};
15
-
16
-Strophe.getStatusString = function(status)
17
-{
18
-    switch (status)
19
-    {
20
-        case Strophe.Status.ERROR:
21
-            return "ERROR";
22
-        case Strophe.Status.CONNECTING:
23
-            return "CONNECTING";
24
-        case Strophe.Status.CONNFAIL:
25
-            return "CONNFAIL";
26
-        case Strophe.Status.AUTHENTICATING:
27
-            return "AUTHENTICATING";
28
-        case Strophe.Status.AUTHFAIL:
29
-            return "AUTHFAIL";
30
-        case Strophe.Status.CONNECTED:
31
-            return "CONNECTED";
32
-        case Strophe.Status.DISCONNECTED:
33
-            return "DISCONNECTED";
34
-        case Strophe.Status.DISCONNECTING:
35
-            return "DISCONNECTING";
36
-        case Strophe.Status.ATTACHED:
37
-            return "ATTACHED";
38
-        default:
39
-            return "unknown";
40
-    }
41
-};

+ 0
- 56
moderatemuc.js View File

1
-/* global $, $iq, config, connection, focusMucJid, forceMuted,
2
-   setAudioMuted, Strophe, toggleAudio */
3
-/**
4
- * Moderate connection plugin.
5
- */
6
-Strophe.addConnectionPlugin('moderate', {
7
-    connection: null,
8
-    init: function (conn) {
9
-        this.connection = conn;
10
-
11
-        this.connection.addHandler(this.onMute.bind(this),
12
-                                   'http://jitsi.org/jitmeet/audio',
13
-                                   'iq',
14
-                                   'set',
15
-                                   null,
16
-                                   null);
17
-    },
18
-    setMute: function (jid, mute) {
19
-        console.info("set mute", mute);
20
-        var iqToFocus = $iq({to: focusMucJid, type: 'set'})
21
-            .c('mute', {
22
-                xmlns: 'http://jitsi.org/jitmeet/audio',
23
-                jid: jid
24
-            })
25
-            .t(mute.toString())
26
-            .up();
27
-
28
-        this.connection.sendIQ(
29
-            iqToFocus,
30
-            function (result) {
31
-                console.log('set mute', result);
32
-            },
33
-            function (error) {
34
-                console.log('set mute error', error);
35
-            });
36
-    },
37
-    onMute: function (iq) {
38
-        var from = iq.getAttribute('from');
39
-        if (from !== focusMucJid) {
40
-            console.warn("Ignored mute from non focus peer");
41
-            return false;
42
-        }
43
-        var mute = $(iq).find('mute');
44
-        if (mute.length) {
45
-            var doMuteAudio = mute.text() === "true";
46
-            setAudioMuted(doMuteAudio);
47
-            forceMuted = doMuteAudio;
48
-        }
49
-        return true;
50
-    },
51
-    eject: function (jid) {
52
-        // We're not the focus, so can't terminate
53
-        //connection.jingle.terminateRemoteByJid(jid, 'kick');
54
-        connection.emuc.kick(jid);
55
-    }
56
-});

+ 2
- 2
modules/API/API.js View File

18
 var commands =
18
 var commands =
19
 {
19
 {
20
     displayName: UI.inputDisplayNameHandler,
20
     displayName: UI.inputDisplayNameHandler,
21
-    muteAudio: toggleAudio,
22
-    muteVideo: toggleVideo,
21
+    muteAudio: UI.toggleAudio,
22
+    muteVideo: UI.toggleVideo,
23
     toggleFilmStrip: UI.toggleFilmStrip,
23
     toggleFilmStrip: UI.toggleFilmStrip,
24
     toggleChat: UI.toggleChat,
24
     toggleChat: UI.toggleChat,
25
     toggleContactList: UI.toggleContactList
25
     toggleContactList: UI.toggleContactList

+ 2
- 2
modules/RTC/DataChannels.js View File

1
-/* global connection, Strophe, updateLargeVideo, focusedVideoSrc*/
1
+/* global Strophe, updateLargeVideo, focusedVideoSrc*/
2
 
2
 
3
 // cache datachannels to avoid garbage collection
3
 // cache datachannels to avoid garbage collection
4
 // https://code.google.com/p/chromium/issues/detail?id=405545
4
 // https://code.google.com/p/chromium/issues/detail?id=405545
91
                             newValue = new Boolean(newValue).valueOf();
91
                             newValue = new Boolean(newValue).valueOf();
92
                         }
92
                         }
93
                     }
93
                     }
94
-                    $(document).trigger('inlastnchanged', [oldValue, newValue]);
94
+                    UI.onLastNChanged(oldValue, newValue);
95
                 }
95
                 }
96
                 else if ("LastNEndpointsChangeEvent" === colibriClass)
96
                 else if ("LastNEndpointsChangeEvent" === colibriClass)
97
                 {
97
                 {

+ 2
- 11
modules/RTC/RTC.js View File

58
     createRemoteStream: function (data, sid, thessrc) {
58
     createRemoteStream: function (data, sid, thessrc) {
59
         var remoteStream = new MediaStream(data, sid, thessrc, eventEmitter,
59
         var remoteStream = new MediaStream(data, sid, thessrc, eventEmitter,
60
             this.getBrowserType());
60
             this.getBrowserType());
61
-        var jid = data.peerjid || connection.emuc.myroomjid;
61
+        var jid = data.peerjid || xmpp.myJid();
62
         if(!this.remoteStreams[jid]) {
62
         if(!this.remoteStreams[jid]) {
63
             this.remoteStreams[jid] = {};
63
             this.remoteStreams[jid] = {};
64
         }
64
         }
144
         RTC.localVideo = this.createLocalStream(stream, type, true);
144
         RTC.localVideo = this.createLocalStream(stream, type, true);
145
         // Stop the stream to trigger onended event for old stream
145
         // Stop the stream to trigger onended event for old stream
146
         oldStream.stop();
146
         oldStream.stop();
147
-        if (activecall) {
148
-            // FIXME: will block switchInProgress on true value in case of exception
149
-            activecall.switchStreams(stream, oldStream, callback);
150
-        } else {
151
-            // We are done immediately
152
-            console.error("No conference handler");
153
-            UI.messageHandler.showError('Error',
154
-                'Unable to switch video stream.');
155
-            callback();
156
-        }
147
+        xmpp.switchStreams(stream, oldStream,callback);
157
     }
148
     }
158
 
149
 
159
 };
150
 };

+ 188
- 72
modules/UI/UI.js View File

17
 var RoomNameGenerator = require("./welcome_page/RoomnameGenerator");
17
 var RoomNameGenerator = require("./welcome_page/RoomnameGenerator");
18
 UI.messageHandler = require("./util/MessageHandler");
18
 UI.messageHandler = require("./util/MessageHandler");
19
 var messageHandler = UI.messageHandler;
19
 var messageHandler = UI.messageHandler;
20
+var Authentication  = require("./authentication/Authentication");
21
+var UIUtil = require("./util/UIUtil");
20
 
22
 
21
 //var eventEmitter = new EventEmitter();
23
 //var eventEmitter = new EventEmitter();
22
-
24
+var roomName = null;
23
 
25
 
24
 
26
 
25
 function setupPrezi()
27
 function setupPrezi()
39
 }
41
 }
40
 
42
 
41
 function setupToolbars() {
43
 function setupToolbars() {
42
-    Toolbar.init();
44
+    Toolbar.init(UI);
43
     Toolbar.setupButtonsFromConfig();
45
     Toolbar.setupButtonsFromConfig();
44
     BottomToolbar.init();
46
     BottomToolbar.init();
45
 }
47
 }
62
     }
64
     }
63
 }
65
 }
64
 
66
 
67
+function onDisposeConference(unload) {
68
+    Toolbar.showAuthenticateButton(false);
69
+};
70
+
71
+function onDisplayNameChanged(jid, displayName) {
72
+    ContactList.onDisplayNameChange(jid, displayName);
73
+    SettingsMenu.onDisplayNameChange(jid, displayName);
74
+    VideoLayout.onDisplayNameChanged(jid, displayName);
75
+}
76
+
65
 function registerListeners() {
77
 function registerListeners() {
66
     RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
78
     RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
67
 
79
 
70
         VideoLayout.onRemoteStreamAdded(stream);
82
         VideoLayout.onRemoteStreamAdded(stream);
71
     }, StreamEventTypes.EVENT_TYPE_REMOTE_CREATED);
83
     }, StreamEventTypes.EVENT_TYPE_REMOTE_CREATED);
72
 
84
 
73
-    // Listen for large video size updates
74
-    document.getElementById('largeVideo')
75
-        .addEventListener('loadedmetadata', function (e) {
76
-            currentVideoWidth = this.videoWidth;
77
-            currentVideoHeight = this.videoHeight;
78
-            VideoLayout.positionLarge(currentVideoWidth, currentVideoHeight);
79
-        });
80
-
85
+    VideoLayout.init();
81
 
86
 
82
     statistics.addAudioLevelListener(function(jid, audioLevel)
87
     statistics.addAudioLevelListener(function(jid, audioLevel)
83
     {
88
     {
104
     desktopsharing.addListener(
109
     desktopsharing.addListener(
105
         Toolbar.changeDesktopSharingButtonState,
110
         Toolbar.changeDesktopSharingButtonState,
106
         DesktopSharingEventTypes.SWITCHING_DONE);
111
         DesktopSharingEventTypes.SWITCHING_DONE);
112
+    xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
113
+    xmpp.addListener(XMPPEvents.KICKED, function () {
114
+        messageHandler.openMessageDialog("Session Terminated",
115
+            "Ouch! You have been kicked out of the meet!");
116
+    });
117
+    xmpp.addListener(XMPPEvents.BRIDGE_DOWN, function () {
118
+        messageHandler.showError("Error",
119
+            "Jitsi Videobridge is currently unavailable. Please try again later!");
120
+    });
121
+    xmpp.addListener(XMPPEvents.USER_ID_CHANGED, Avatar.setUserAvatar);
122
+    xmpp.addListener(XMPPEvents.CHANGED_STREAMS, function (jid, changedStreams) {
123
+        for(stream in changedStreams)
124
+        {
125
+            // might need to update the direction if participant just went from sendrecv to recvonly
126
+            if (stream.type === 'video' || stream.type === 'screen') {
127
+                var el = $('#participant_'  + Strophe.getResourceFromJid(jid) + '>video');
128
+                switch (stream.direction) {
129
+                    case 'sendrecv':
130
+                        el.show();
131
+                        break;
132
+                    case 'recvonly':
133
+                        el.hide();
134
+                        // FIXME: Check if we have to change large video
135
+                        //VideoLayout.updateLargeVideo(el);
136
+                        break;
137
+                }
138
+            }
139
+        }
107
 
140
 
108
-
141
+    });
142
+    xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, onDisplayNameChanged);
143
+    xmpp.addListener(XMPPEvents.MUC_JOINED, onMucJoined);
109
 }
144
 }
110
 
145
 
111
 function bindEvents()
146
 function bindEvents()
117
         function () {
152
         function () {
118
             VideoLayout.resizeLargeVideoContainer();
153
             VideoLayout.resizeLargeVideoContainer();
119
             VideoLayout.positionLarge();
154
             VideoLayout.positionLarge();
120
-            isFullScreen = document.fullScreen ||
121
-                document.mozFullScreen ||
122
-                document.webkitIsFullScreen;
123
-
124
         }
155
         }
125
     );
156
     );
126
 
157
 
255
 
286
 
256
 };
287
 };
257
 
288
 
258
-
259
-UI.setUserAvatar = function (jid, id) {
260
-    Avatar.setUserAvatar(jid, id);
261
-};
262
-
263
 UI.toggleSmileys = function () {
289
 UI.toggleSmileys = function () {
264
     Chat.toggleSmileys();
290
     Chat.toggleSmileys();
265
 };
291
 };
278
     return Chat.updateChatConversation(from, displayName, message);
304
     return Chat.updateChatConversation(from, displayName, message);
279
 };
305
 };
280
 
306
 
281
-UI.onMucJoined = function (jid, info) {
307
+function onMucJoined(jid, info) {
282
     Toolbar.updateRoomUrl(window.location.href);
308
     Toolbar.updateRoomUrl(window.location.href);
283
     document.getElementById('localNick').appendChild(
309
     document.getElementById('localNick').appendChild(
284
         document.createTextNode(Strophe.getResourceFromJid(jid) + ' (me)')
310
         document.createTextNode(Strophe.getResourceFromJid(jid) + ' (me)')
293
 
319
 
294
     // Show authenticate button if needed
320
     // Show authenticate button if needed
295
     Toolbar.showAuthenticateButton(
321
     Toolbar.showAuthenticateButton(
296
-            Moderator.isExternalAuthEnabled() && !Moderator.isModerator());
322
+            xmpp.isExternalAuthEnabled() && !xmpp.isModerator());
297
 
323
 
298
     var displayName = !config.displayJids
324
     var displayName = !config.displayJids
299
         ? info.displayName : Strophe.getResourceFromJid(jid);
325
         ? info.displayName : Strophe.getResourceFromJid(jid);
300
 
326
 
301
     if (displayName)
327
     if (displayName)
302
-        $(document).trigger('displaynamechanged',
303
-            ['localVideoContainer', displayName + ' (me)']);
304
-};
328
+        onDisplayNameChanged('localVideoContainer', displayName + ' (me)');
329
+}
305
 
330
 
306
 UI.initEtherpad = function (name) {
331
 UI.initEtherpad = function (name) {
307
     Etherpad.init(name);
332
     Etherpad.init(name);
357
 UI.onLocalRoleChange = function (jid, info, pres) {
382
 UI.onLocalRoleChange = function (jid, info, pres) {
358
 
383
 
359
     console.info("My role changed, new role: " + info.role);
384
     console.info("My role changed, new role: " + info.role);
360
-    var isModerator = Moderator.isModerator();
385
+    var isModerator = xmpp.isModerator();
361
 
386
 
362
     VideoLayout.showModeratorIndicator();
387
     VideoLayout.showModeratorIndicator();
363
     Toolbar.showAuthenticateButton(
388
     Toolbar.showAuthenticateButton(
364
-            Moderator.isExternalAuthEnabled() && !isModerator);
389
+            xmpp.isExternalAuthEnabled() && !isModerator);
365
 
390
 
366
     if (isModerator) {
391
     if (isModerator) {
367
-        Toolbar.closeAuthenticationWindow();
392
+        Authentication.closeAuthenticationWindow();
368
         messageHandler.notify(
393
         messageHandler.notify(
369
             'Me', 'connected', 'Moderator rights granted !');
394
             'Me', 'connected', 'Moderator rights granted !');
370
     }
395
     }
371
 };
396
 };
372
 
397
 
373
-UI.onDisposeConference = function (unload) {
374
-    Toolbar.showAuthenticateButton(false);
375
-};
376
-
377
 UI.onModeratorStatusChanged = function (isModerator) {
398
 UI.onModeratorStatusChanged = function (isModerator) {
378
 
399
 
379
     Toolbar.showSipCallButton(isModerator);
400
     Toolbar.showSipCallButton(isModerator);
414
     );
435
     );
415
 };
436
 };
416
 
437
 
417
-UI.onAuthenticationRequired = function () {
418
-    // This is the loop that will wait for the room to be created by
419
-    // someone else. 'auth_required.moderator' will bring us back here.
420
-    authRetryId = window.setTimeout(
421
-        function () {
422
-            Moderator.allocateConferenceFocus(roomName, doJoinAfterFocus);
423
-        }, 5000);
424
-    // Show prompt only if it's not open
425
-    if (authDialog !== null) {
426
-        return;
427
-    }
428
-    // extract room name from 'room@muc.server.net'
429
-    var room = roomName.substr(0, roomName.indexOf('@'));
430
-
431
-    authDialog = messageHandler.openDialog(
432
-        'Stop',
433
-            'Authentication is required to create room:<br/><b>' + room +
434
-            '</b></br> You can either authenticate to create the room or ' +
435
-            'just wait for someone else to do so.',
436
-        true,
437
-        {
438
-            Authenticate: 'authNow'
439
-        },
440
-        function (onSubmitEvent, submitValue) {
441
-
442
-            // Do not close the dialog yet
443
-            onSubmitEvent.preventDefault();
444
-
445
-            // Open login popup
446
-            if (submitValue === 'authNow') {
447
-                Toolbar.authenticateClicked();
448
-            }
449
-        }
450
-    );
438
+UI.onAuthenticationRequired = function (intervalCallback) {
439
+    Authentication.openAuthenticationDialog(
440
+        roomName, intervalCallback, function () {
441
+            Toolbar.authenticateClicked();
442
+        });
451
 };
443
 };
452
 
444
 
453
 UI.setRecordingButtonState = function (state) {
445
 UI.setRecordingButtonState = function (state) {
511
 };
503
 };
512
 
504
 
513
 UI.generateRoomName = function() {
505
 UI.generateRoomName = function() {
506
+    if(roomName)
507
+        return roomName;
514
     var roomnode = null;
508
     var roomnode = null;
515
     var path = window.location.pathname;
509
     var path = window.location.pathname;
516
 
510
 
540
     }
534
     }
541
 
535
 
542
     roomName = roomnode + '@' + config.hosts.muc;
536
     roomName = roomnode + '@' + config.hosts.muc;
537
+    return roomName;
543
 };
538
 };
544
 
539
 
545
 
540
 
556
     return ToolbarToggler.dockToolbar(isDock);
551
     return ToolbarToggler.dockToolbar(isDock);
557
 };
552
 };
558
 
553
 
554
+UI.getCreadentials = function () {
555
+    return {
556
+        bosh: document.getElementById('boshURL').value,
557
+        password: document.getElementById('password').value,
558
+        jid: document.getElementById('jid').value
559
+    };
560
+};
561
+
562
+UI.disableConnect = function () {
563
+    document.getElementById('connect').disabled = true;
564
+};
565
+
566
+UI.showLoginPopup = function(callback)
567
+{
568
+    console.log('password is required');
569
+
570
+    UI.messageHandler.openTwoButtonDialog(null,
571
+            '<h2>Password required</h2>' +
572
+            '<input id="passwordrequired.username" type="text" placeholder="user@domain.net" autofocus>' +
573
+            '<input id="passwordrequired.password" type="password" placeholder="user password">',
574
+        true,
575
+        "Ok",
576
+        function (e, v, m, f) {
577
+            if (v) {
578
+                var username = document.getElementById('passwordrequired.username');
579
+                var password = document.getElementById('passwordrequired.password');
580
+
581
+                if (username.value !== null && password.value != null) {
582
+                    callback(username.value, password.value);
583
+                }
584
+            }
585
+        },
586
+        function (event) {
587
+            document.getElementById('passwordrequired.username').focus();
588
+        }
589
+    );
590
+}
591
+
592
+UI.checkForNicknameAndJoin = function () {
593
+
594
+    Authentication.closeAuthenticationDialog();
595
+    Authentication.stopInterval();
596
+
597
+    var nick = null;
598
+    if (config.useNicks) {
599
+        nick = window.prompt('Your nickname (optional)');
600
+    }
601
+    xmpp.joinRooom(roomName, config.useNicks, nick);
602
+}
603
+
604
+
559
 function dump(elem, filename) {
605
 function dump(elem, filename) {
560
     elem = elem.parentNode;
606
     elem = elem.parentNode;
561
     elem.download = filename || 'meetlog.json';
607
     elem.download = filename || 'meetlog.json';
562
     elem.href = 'data:application/json;charset=utf-8,\n';
608
     elem.href = 'data:application/json;charset=utf-8,\n';
563
-    var data = {};
564
-    if (connection.jingle) {
565
-        data = connection.jingle.populateData();
566
-    }
609
+    var data = xmpp.populateData();
567
     var metadata = {};
610
     var metadata = {};
568
     metadata.time = new Date();
611
     metadata.time = new Date();
569
     metadata.url = window.location.href;
612
     metadata.url = window.location.href;
570
     metadata.ua = navigator.userAgent;
613
     metadata.ua = navigator.userAgent;
571
-    if (connection.logger) {
572
-        metadata.xmpp = connection.logger.log;
614
+    var log = xmpp.getLogger();
615
+    if (log) {
616
+        metadata.xmpp = log;
573
     }
617
     }
574
     data.metadata = metadata;
618
     data.metadata = metadata;
575
     elem.href += encodeURIComponent(JSON.stringify(data, null, '  '));
619
     elem.href += encodeURIComponent(JSON.stringify(data, null, '  '));
576
     return false;
620
     return false;
577
 }
621
 }
578
 
622
 
623
+UI.getRoomName = function () {
624
+    return roomName;
625
+}
626
+
627
+/**
628
+ * Mutes/unmutes the local video.
629
+ *
630
+ * @param mute <tt>true</tt> to mute the local video; otherwise, <tt>false</tt>
631
+ * @param options an object which specifies optional arguments such as the
632
+ * <tt>boolean</tt> key <tt>byUser</tt> with default value <tt>true</tt> which
633
+ * specifies whether the method was initiated in response to a user command (in
634
+ * contrast to an automatic decision taken by the application logic)
635
+ */
636
+function setVideoMute(mute, options) {
637
+    xmpp.setVideoMute(
638
+        mute,
639
+        function (mute) {
640
+            var video = $('#video');
641
+            var communicativeClass = "icon-camera";
642
+            var muteClass = "icon-camera icon-camera-disabled";
643
+
644
+            if (mute) {
645
+                video.removeClass(communicativeClass);
646
+                video.addClass(muteClass);
647
+            } else {
648
+                video.removeClass(muteClass);
649
+                video.addClass(communicativeClass);
650
+            }
651
+        },
652
+        options);
653
+}
654
+
655
+/**
656
+ * Mutes/unmutes the local video.
657
+ */
658
+UI.toggleVideo = function () {
659
+    UIUtil.buttonClick("#video", "icon-camera icon-camera-disabled");
660
+
661
+    setVideoMute(!RTC.localVideo.isMuted());
662
+};
663
+
664
+/**
665
+ * Mutes / unmutes audio for the local participant.
666
+ */
667
+UI.toggleAudio = function() {
668
+    UI.setAudioMuted(!RTC.localAudio.isMuted());
669
+};
670
+
671
+/**
672
+ * Sets muted audio state for the local participant.
673
+ */
674
+UI.setAudioMuted = function (mute) {
675
+
676
+    if(!xmpp.setAudioMute(mute, function () {
677
+        UI.showLocalAudioIndicator(mute);
678
+
679
+        UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
680
+    }))
681
+    {
682
+        // We still click the button.
683
+        UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
684
+        return;
685
+    }
686
+
687
+}
688
+
689
+UI.onLastNChanged = function (oldValue, newValue) {
690
+    if (config.muteLocalVideoIfNotInLastN) {
691
+        setVideoMute(!newValue, { 'byUser': false });
692
+    }
693
+}
694
+
579
 module.exports = UI;
695
 module.exports = UI;
580
 
696
 

+ 4
- 4
modules/UI/audio_levels/AudioLevels.js View File

87
         drawContext.drawImage(canvasCache, 0, 0);
87
         drawContext.drawImage(canvasCache, 0, 0);
88
 
88
 
89
         if(resourceJid === AudioLevels.LOCAL_LEVEL) {
89
         if(resourceJid === AudioLevels.LOCAL_LEVEL) {
90
-            if(!connection.emuc.myroomjid) {
90
+            if(!xmpp.myJid()) {
91
                 return;
91
                 return;
92
             }
92
             }
93
-            resourceJid = Strophe.getResourceFromJid(connection.emuc.myroomjid);
93
+            resourceJid = xmpp.myResource();
94
         }
94
         }
95
 
95
 
96
         if(resourceJid  === largeVideoResourceJid) {
96
         if(resourceJid  === largeVideoResourceJid) {
221
     function getVideoSpanId(resourceJid) {
221
     function getVideoSpanId(resourceJid) {
222
         var videoSpanId = null;
222
         var videoSpanId = null;
223
         if (resourceJid === AudioLevels.LOCAL_LEVEL
223
         if (resourceJid === AudioLevels.LOCAL_LEVEL
224
-                || (connection.emuc.myroomjid && resourceJid
225
-                    === Strophe.getResourceFromJid(connection.emuc.myroomjid)))
224
+                || (xmpp.myResource() && resourceJid
225
+                    === xmpp.myResource()))
226
             videoSpanId = 'localVideoContainer';
226
             videoSpanId = 'localVideoContainer';
227
         else
227
         else
228
             videoSpanId = 'participant_' + resourceJid;
228
             videoSpanId = 'participant_' + resourceJid;

+ 84
- 0
modules/UI/authentication/Authentication.js View File

1
+/* Initial "authentication required" dialog */
2
+var authDialog = null;
3
+/* Loop retry ID that wits for other user to create the room */
4
+var authRetryId = null;
5
+var authenticationWindow = null;
6
+
7
+var Authentication = {
8
+    openAuthenticationDialog: function (roomName, intervalCallback, callback) {
9
+        // This is the loop that will wait for the room to be created by
10
+        // someone else. 'auth_required.moderator' will bring us back here.
11
+        authRetryId = window.setTimeout(intervalCallback , 5000);
12
+        // Show prompt only if it's not open
13
+        if (authDialog !== null) {
14
+            return;
15
+        }
16
+        // extract room name from 'room@muc.server.net'
17
+        var room = roomName.substr(0, roomName.indexOf('@'));
18
+
19
+        authDialog = messageHandler.openDialog(
20
+            'Stop',
21
+                'Authentication is required to create room:<br/><b>' + room +
22
+                '</b></br> You can either authenticate to create the room or ' +
23
+                'just wait for someone else to do so.',
24
+            true,
25
+            {
26
+                Authenticate: 'authNow'
27
+            },
28
+            function (onSubmitEvent, submitValue) {
29
+
30
+                // Do not close the dialog yet
31
+                onSubmitEvent.preventDefault();
32
+
33
+                // Open login popup
34
+                if (submitValue === 'authNow') {
35
+                    callback();
36
+                }
37
+            }
38
+        );
39
+    },
40
+    closeAuthenticationWindow:function () {
41
+        if (authenticationWindow) {
42
+            authenticationWindow.close();
43
+            authenticationWindow = null;
44
+        }
45
+    },
46
+    focusAuthenticationWindow: function () {
47
+        // If auth window exists just bring it to the front
48
+        if (authenticationWindow) {
49
+            authenticationWindow.focus();
50
+            return;
51
+        }
52
+    },
53
+    closeAuthenticationDialog: function () {
54
+        // Close authentication dialog if opened
55
+        if (authDialog) {
56
+            UI.messageHandler.closeDialog();
57
+            authDialog = null;
58
+        }
59
+    },
60
+    createAuthenticationWindow: function (callback, url) {
61
+        authenticationWindow = messageHandler.openCenteredPopup(
62
+            url, 910, 660,
63
+            // On closed
64
+            function () {
65
+                // Close authentication dialog if opened
66
+                if (authDialog) {
67
+                    messageHandler.closeDialog();
68
+                    authDialog = null;
69
+                }
70
+                callback();
71
+                authenticationWindow = null;
72
+            });
73
+        return authenticationWindow;
74
+    },
75
+    stopInterval: function () {
76
+        // Clear retry interval, so that we don't call 'doJoinAfterFocus' twice
77
+        if (authRetryId) {
78
+            window.clearTimeout(authRetryId);
79
+            authRetryId = null;
80
+        }
81
+    }
82
+};
83
+
84
+module.exports = Authentication;

+ 5
- 5
modules/UI/avatar/Avatar.js View File

12
 function isUserMuted(jid) {
12
 function isUserMuted(jid) {
13
     // XXX(gp) we may want to rename this method to something like
13
     // XXX(gp) we may want to rename this method to something like
14
     // isUserStreaming, for example.
14
     // isUserStreaming, for example.
15
-    if (jid && jid != connection.emuc.myroomjid) {
15
+    if (jid && jid != xmpp.myJid()) {
16
         var resource = Strophe.getResourceFromJid(jid);
16
         var resource = Strophe.getResourceFromJid(jid);
17
         if (!require("../videolayout/VideoLayout").isInLastN(resource)) {
17
         if (!require("../videolayout/VideoLayout").isInLastN(resource)) {
18
             return true;
18
             return true;
26
 }
26
 }
27
 
27
 
28
 function getGravatarUrl(id, size) {
28
 function getGravatarUrl(id, size) {
29
-    if(id === connection.emuc.myroomjid || !id) {
29
+    if(id === xmpp.myJid() || !id) {
30
         id = Settings.getSettings().uid;
30
         id = Settings.getSettings().uid;
31
     }
31
     }
32
     return 'https://www.gravatar.com/avatar/' +
32
     return 'https://www.gravatar.com/avatar/' +
57
 
57
 
58
         // set the avatar in the settings menu if it is local user and get the
58
         // set the avatar in the settings menu if it is local user and get the
59
         // local video container
59
         // local video container
60
-        if (jid === connection.emuc.myroomjid) {
60
+        if (jid === xmpp.myJid()) {
61
             $('#avatar').get(0).src = thumbUrl;
61
             $('#avatar').get(0).src = thumbUrl;
62
             thumbnail = $('#localVideoContainer');
62
             thumbnail = $('#localVideoContainer');
63
         }
63
         }
100
             var video = $('#participant_' + resourceJid + '>video');
100
             var video = $('#participant_' + resourceJid + '>video');
101
             var avatar = $('#avatar_' + resourceJid);
101
             var avatar = $('#avatar_' + resourceJid);
102
 
102
 
103
-            if (jid === connection.emuc.myroomjid) {
103
+            if (jid === xmpp.myJid()) {
104
                 video = $('#localVideoWrapper>video');
104
                 video = $('#localVideoWrapper>video');
105
             }
105
             }
106
             if (show === undefined || show === null) {
106
             if (show === undefined || show === null) {
130
      */
130
      */
131
     updateActiveSpeakerAvatarSrc: function (jid) {
131
     updateActiveSpeakerAvatarSrc: function (jid) {
132
         if (!jid) {
132
         if (!jid) {
133
-            jid = connection.emuc.findJidFromResource(
133
+            jid = xmpp.findJidFromResource(
134
                 require("../videolayout/VideoLayout").getLargeVideoState().userResourceJid);
134
                 require("../videolayout/VideoLayout").getLargeVideoState().userResourceJid);
135
         }
135
         }
136
         var avatar = $("#activeSpeakerAvatar")[0];
136
         var avatar = $("#activeSpeakerAvatar")[0];

+ 2
- 3
modules/UI/etherpad/Etherpad.js View File

1
-/* global $, config, connection, dockToolbar, Moderator,
1
+/* global $, config, dockToolbar,
2
    setLargeVideoVisible, Util */
2
    setLargeVideoVisible, Util */
3
 
3
 
4
 var VideoLayout = require("../videolayout/VideoLayout");
4
 var VideoLayout = require("../videolayout/VideoLayout");
30
  * Shares the Etherpad name with other participants.
30
  * Shares the Etherpad name with other participants.
31
  */
31
  */
32
 function shareEtherpad() {
32
 function shareEtherpad() {
33
-    connection.emuc.addEtherpadToPresence(etherpadName);
34
-    connection.emuc.sendPresence();
33
+    xmpp.addToPresence("etherpad", etherpadName);
35
 }
34
 }
36
 
35
 
37
 /**
36
 /**

+ 6
- 10
modules/UI/prezi/Prezi.js View File

30
      * to load.
30
      * to load.
31
      */
31
      */
32
     openPreziDialog: function() {
32
     openPreziDialog: function() {
33
-        var myprezi = connection.emuc.getPrezi(connection.emuc.myroomjid);
33
+        var myprezi = xmpp.getPrezi();
34
         if (myprezi) {
34
         if (myprezi) {
35
             messageHandler.openTwoButtonDialog("Remove Prezi",
35
             messageHandler.openTwoButtonDialog("Remove Prezi",
36
                 "Are you sure you would like to remove your Prezi?",
36
                 "Are you sure you would like to remove your Prezi?",
38
                 "Remove",
38
                 "Remove",
39
                 function(e,v,m,f) {
39
                 function(e,v,m,f) {
40
                     if(v) {
40
                     if(v) {
41
-                        connection.emuc.removePreziFromPresence();
42
-                        connection.emuc.sendPresence();
41
+                        xmpp.removePreziFromPresence();
43
                     }
42
                     }
44
                 }
43
                 }
45
             );
44
             );
91
                                         return false;
90
                                         return false;
92
                                     }
91
                                     }
93
                                     else {
92
                                     else {
94
-                                        connection.emuc
95
-                                            .addPreziToPresence(urlValue, 0);
96
-                                        connection.emuc.sendPresence();
93
+                                        xmpp.addToPresence("prezi", urlValue);
97
                                         $.prompt.close();
94
                                         $.prompt.close();
98
                                     }
95
                                     }
99
                                 }
96
                                 }
151
     VideoLayout.resizeThumbnails();
148
     VideoLayout.resizeThumbnails();
152
 
149
 
153
     var controlsEnabled = false;
150
     var controlsEnabled = false;
154
-    if (jid === connection.emuc.myroomjid)
151
+    if (jid === xmpp.myJid())
155
         controlsEnabled = true;
152
         controlsEnabled = true;
156
 
153
 
157
     setPresentationVisible(true);
154
     setPresentationVisible(true);
191
     preziPlayer.on(PreziPlayer.EVENT_STATUS, function(event) {
188
     preziPlayer.on(PreziPlayer.EVENT_STATUS, function(event) {
192
         console.log("prezi status", event.value);
189
         console.log("prezi status", event.value);
193
         if (event.value == PreziPlayer.STATUS_CONTENT_READY) {
190
         if (event.value == PreziPlayer.STATUS_CONTENT_READY) {
194
-            if (jid != connection.emuc.myroomjid)
191
+            if (jid != xmpp.myJid())
195
                 preziPlayer.flyToStep(currentSlide);
192
                 preziPlayer.flyToStep(currentSlide);
196
         }
193
         }
197
     });
194
     });
198
 
195
 
199
     preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function(event) {
196
     preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function(event) {
200
         console.log("event value", event.value);
197
         console.log("event value", event.value);
201
-        connection.emuc.addCurrentSlideToPresence(event.value);
202
-        connection.emuc.sendPresence();
198
+        xmpp.addToPresence("preziSlide", event.value);
203
     });
199
     });
204
 
200
 
205
     $("#" + elementId).css( 'background-image',
201
     $("#" + elementId).css( 'background-image',

+ 3
- 2
modules/UI/side_pannels/SidePanelToggler.js View File

4
 var SettingsMenu = require("./settings/SettingsMenu");
4
 var SettingsMenu = require("./settings/SettingsMenu");
5
 var VideoLayout = require("../videolayout/VideoLayout");
5
 var VideoLayout = require("../videolayout/VideoLayout");
6
 var ToolbarToggler = require("../toolbars/ToolbarToggler");
6
 var ToolbarToggler = require("../toolbars/ToolbarToggler");
7
+var UIUtil = require("../util/UIUtil");
7
 
8
 
8
 /**
9
 /**
9
  * Toggler for the chat, contact list, settings menu, etc..
10
  * Toggler for the chat, contact list, settings menu, etc..
110
      * @param onClose function to be called if the window is going to be closed
111
      * @param onClose function to be called if the window is going to be closed
111
      */
112
      */
112
     var toggle = function(object, selector, onOpenComplete, onOpen, onClose) {
113
     var toggle = function(object, selector, onOpenComplete, onOpen, onClose) {
113
-        buttonClick(buttons[selector], "active");
114
+        UIUtil.buttonClick(buttons[selector], "active");
114
 
115
 
115
         if (object.isVisible()) {
116
         if (object.isVisible()) {
116
             $("#toast-container").animate({
117
             $("#toast-container").animate({
140
 
141
 
141
             if(currentlyOpen) {
142
             if(currentlyOpen) {
142
                 var current = $(currentlyOpen);
143
                 var current = $(currentlyOpen);
143
-                buttonClick(buttons[currentlyOpen], "active");
144
+                UIUtil.buttonClick(buttons[currentlyOpen], "active");
144
                 current.css('z-index', 4);
145
                 current.css('z-index', 4);
145
                 setTimeout(function () {
146
                 setTimeout(function () {
146
                     current.css('display', 'none');
147
                     current.css('display', 'none');

+ 4
- 5
modules/UI/side_pannels/chat/Chat.js View File

1
-/* global $, Util, connection, nickname:true, showToolbar */
1
+/* global $, Util, nickname:true, showToolbar */
2
 var Replacement = require("./Replacement");
2
 var Replacement = require("./Replacement");
3
 var CommandsProcessor = require("./Commands");
3
 var CommandsProcessor = require("./Commands");
4
 var ToolbarToggler = require("../../toolbars/ToolbarToggler");
4
 var ToolbarToggler = require("../../toolbars/ToolbarToggler");
184
                     nickname = val;
184
                     nickname = val;
185
                     window.localStorage.displayname = nickname;
185
                     window.localStorage.displayname = nickname;
186
 
186
 
187
-                    connection.emuc.addDisplayNameToPresence(nickname);
188
-                    connection.emuc.sendPresence();
187
+                    xmpp.addToPresence("displayName", nickname);
189
 
188
 
190
                     Chat.setChatConversationMode(true);
189
                     Chat.setChatConversationMode(true);
191
 
190
 
208
                 else
207
                 else
209
                 {
208
                 {
210
                     var message = Util.escapeHtml(value);
209
                     var message = Util.escapeHtml(value);
211
-                    connection.emuc.sendMessage(message, nickname);
210
+                    xmpp.sendChatMessage(message, nickname);
212
                 }
211
                 }
213
             }
212
             }
214
         });
213
         });
234
     my.updateChatConversation = function (from, displayName, message) {
233
     my.updateChatConversation = function (from, displayName, message) {
235
         var divClassName = '';
234
         var divClassName = '';
236
 
235
 
237
-        if (connection.emuc.myroomjid === from) {
236
+        if (xmpp.myJid() === from) {
238
             divClassName = "localuser";
237
             divClassName = "localuser";
239
         }
238
         }
240
         else {
239
         else {

+ 1
- 1
modules/UI/side_pannels/chat/Commands.js View File

32
 function processTopic(commandArguments)
32
 function processTopic(commandArguments)
33
 {
33
 {
34
     var topic = Util.escapeHtml(commandArguments);
34
     var topic = Util.escapeHtml(commandArguments);
35
-    connection.emuc.setSubject(topic);
35
+    xmpp.setSubject(topic);
36
 }
36
 }
37
 
37
 
38
 /**
38
 /**

+ 13
- 18
modules/UI/side_pannels/contactlist/ContactList.js View File

46
 }
46
 }
47
 
47
 
48
 
48
 
49
-/**
50
- * Indicates that the display name has changed.
51
- */
52
-$(document).bind(   'displaynamechanged',
53
-    function (event, peerJid, displayName) {
54
-        if (peerJid === 'localVideoContainer')
55
-            peerJid = connection.emuc.myroomjid;
56
-
57
-        var resourceJid = Strophe.getResourceFromJid(peerJid);
58
-
59
-        var contactName = $('#contactlist #' + resourceJid + '>p');
60
-
61
-        if (contactName && displayName && displayName.length > 0)
62
-            contactName.html(displayName);
63
-    });
64
-
65
-
66
 function stopGlowing(glower) {
49
 function stopGlowing(glower) {
67
     window.clearInterval(notificationInterval);
50
     window.clearInterval(notificationInterval);
68
     notificationInterval = false;
51
     notificationInterval = false;
127
 
110
 
128
         var clElement = contactlist.get(0);
111
         var clElement = contactlist.get(0);
129
 
112
 
130
-        if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid)
113
+        if (resourceJid === xmpp.myResource()
131
             && $('#contactlist>ul .title')[0].nextSibling.nextSibling) {
114
             && $('#contactlist>ul .title')[0].nextSibling.nextSibling) {
132
             clElement.insertBefore(newContact,
115
             clElement.insertBefore(newContact,
133
                 $('#contactlist>ul .title')[0].nextSibling.nextSibling);
116
                 $('#contactlist>ul .title')[0].nextSibling.nextSibling);
182
         } else {
165
         } else {
183
             contact.removeClass('clickable');
166
             contact.removeClass('clickable');
184
         }
167
         }
168
+    },
169
+
170
+    onDisplayNameChange: function (peerJid, displayName) {
171
+        if (peerJid === 'localVideoContainer')
172
+            peerJid = xmpp.myJid();
173
+
174
+        var resourceJid = Strophe.getResourceFromJid(peerJid);
175
+
176
+        var contactName = $('#contactlist #' + resourceJid + '>p');
177
+
178
+        if (contactName && displayName && displayName.length > 0)
179
+            contactName.html(displayName);
185
     }
180
     }
186
 };
181
 };
187
 
182
 

+ 10
- 10
modules/UI/side_pannels/settings/SettingsMenu.js View File

10
 
10
 
11
         if(newDisplayName) {
11
         if(newDisplayName) {
12
             var displayName = Settings.setDisplayName(newDisplayName);
12
             var displayName = Settings.setDisplayName(newDisplayName);
13
-            connection.emuc.addDisplayNameToPresence(displayName);
13
+            xmpp.addToPresence("displayName", displayName, true);
14
         }
14
         }
15
 
15
 
16
 
16
 
17
-        connection.emuc.addEmailToPresence(newEmail);
17
+        xmpp.addToPresence("email", newEmail);
18
         var email = Settings.setEmail(newEmail);
18
         var email = Settings.setEmail(newEmail);
19
 
19
 
20
 
20
 
21
-        connection.emuc.sendPresence();
22
-        Avatar.setUserAvatar(connection.emuc.myroomjid, email);
21
+        Avatar.setUserAvatar(xmpp.myJid(), email);
23
     },
22
     },
24
 
23
 
25
     isVisible: function() {
24
     isVisible: function() {
29
     setDisplayName: function(newDisplayName) {
28
     setDisplayName: function(newDisplayName) {
30
         var displayName = Settings.setDisplayName(newDisplayName);
29
         var displayName = Settings.setDisplayName(newDisplayName);
31
         $('#setDisplayName').get(0).value = displayName;
30
         $('#setDisplayName').get(0).value = displayName;
31
+    },
32
+
33
+    onDisplayNameChange: function(peerJid, newDisplayName) {
34
+        if(peerJid === 'localVideoContainer' ||
35
+            peerJid === xmpp.myJid()) {
36
+            this.setDisplayName(newDisplayName);
37
+        }
32
     }
38
     }
33
 };
39
 };
34
 
40
 
35
-$(document).bind('displaynamechanged', function(event, peerJid, newDisplayName) {
36
-    if(peerJid === 'localVideoContainer' ||
37
-        peerJid === connection.emuc.myroomjid) {
38
-        SettingsMenu.setDisplayName(newDisplayName);
39
-    }
40
-});
41
 
41
 
42
 module.exports = SettingsMenu;
42
 module.exports = SettingsMenu;

+ 47
- 45
modules/UI/toolbars/Toolbar.js View File

1
-/* global $, buttonClick, config, lockRoom,  Moderator, roomName,
2
-   setSharedKey, sharedKey, Util */
1
+/* global $, buttonClick, config, lockRoom,
2
+   setSharedKey, Util */
3
 var messageHandler = require("../util/MessageHandler");
3
 var messageHandler = require("../util/MessageHandler");
4
 var BottomToolbar = require("./BottomToolbar");
4
 var BottomToolbar = require("./BottomToolbar");
5
 var Prezi = require("../prezi/Prezi");
5
 var Prezi = require("../prezi/Prezi");
6
 var Etherpad = require("../etherpad/Etherpad");
6
 var Etherpad = require("../etherpad/Etherpad");
7
 var PanelToggler = require("../side_pannels/SidePanelToggler");
7
 var PanelToggler = require("../side_pannels/SidePanelToggler");
8
+var Authentication = require("../authentication/Authentication");
9
+var UIUtil = require("../util/UIUtil");
8
 
10
 
9
 var roomUrl = null;
11
 var roomUrl = null;
10
 var sharedKey = '';
12
 var sharedKey = '';
11
-var authenticationWindow = null;
13
+var UI = null;
12
 
14
 
13
 var buttonHandlers =
15
 var buttonHandlers =
14
 {
16
 {
15
     "toolbar_button_mute": function () {
17
     "toolbar_button_mute": function () {
16
-        return toggleAudio();
18
+        return UI.toggleAudio();
17
     },
19
     },
18
     "toolbar_button_camera": function () {
20
     "toolbar_button_camera": function () {
19
-        return toggleVideo();
21
+        return UI.toggleVideo();
20
     },
22
     },
21
     "toolbar_button_authentication": function () {
23
     "toolbar_button_authentication": function () {
22
         return Toolbar.authenticateClicked();
24
         return Toolbar.authenticateClicked();
44
     },
46
     },
45
     "toolbar_button_fullScreen": function()
47
     "toolbar_button_fullScreen": function()
46
     {
48
     {
47
-        buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");
49
+        UIUtil.buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");
48
         return Toolbar.toggleFullScreen();
50
         return Toolbar.toggleFullScreen();
49
     },
51
     },
50
     "toolbar_button_sip": function () {
52
     "toolbar_button_sip": function () {
59
 };
61
 };
60
 
62
 
61
 function hangup() {
63
 function hangup() {
62
-    disposeConference();
63
-    sessionTerminated = true;
64
-    connection.emuc.doLeave();
64
+    xmpp.disposeConference();
65
     if(config.enableWelcomePage)
65
     if(config.enableWelcomePage)
66
     {
66
     {
67
         setTimeout(function()
67
         setTimeout(function()
90
  */
90
  */
91
 
91
 
92
 function toggleRecording() {
92
 function toggleRecording() {
93
-    Recording.toggleRecording();
93
+    xmpp.toggleRecording(function (callback) {
94
+        UI.messageHandler.openTwoButtonDialog(null,
95
+                '<h2>Enter recording token</h2>' +
96
+                '<input id="recordingToken" type="text" ' +
97
+                'placeholder="token" autofocus>',
98
+            false,
99
+            "Save",
100
+            function (e, v, m, f) {
101
+                if (v) {
102
+                    var token = document.getElementById('recordingToken');
103
+
104
+                    if (token.value) {
105
+                        callback(Util.escapeHtml(token.value));
106
+                    }
107
+                }
108
+            },
109
+            function (event) {
110
+                document.getElementById('recordingToken').focus();
111
+            },
112
+            function () {
113
+            }
114
+        );
115
+    }, Toolbar.setRecordingButtonState, Toolbar.setRecordingButtonState);
94
 }
116
 }
95
 
117
 
96
 /**
118
 /**
101
     if (lock)
123
     if (lock)
102
         currentSharedKey = sharedKey;
124
         currentSharedKey = sharedKey;
103
 
125
 
104
-    connection.emuc.lockRoom(currentSharedKey, function (res) {
126
+    xmpp.lockRoom(currentSharedKey, function (res) {
105
         // password is required
127
         // password is required
106
         if (sharedKey)
128
         if (sharedKey)
107
         {
129
         {
183
             if (v) {
205
             if (v) {
184
                 var numberInput = document.getElementById('sipNumber');
206
                 var numberInput = document.getElementById('sipNumber');
185
                 if (numberInput.value) {
207
                 if (numberInput.value) {
186
-                    connection.rayo.dial(
187
-                        numberInput.value, 'fromnumber',
188
-                        roomName, sharedKey);
208
+                    xmpp.dial(numberInput.value, 'fromnumber',
209
+                        UI.getRoomName(), sharedKey);
189
                 }
210
                 }
190
             }
211
             }
191
         },
212
         },
197
 
218
 
198
 var Toolbar = (function (my) {
219
 var Toolbar = (function (my) {
199
 
220
 
200
-    my.init = function () {
221
+    my.init = function (ui) {
201
         for(var k in buttonHandlers)
222
         for(var k in buttonHandlers)
202
             $("#" + k).click(buttonHandlers[k]);
223
             $("#" + k).click(buttonHandlers[k]);
224
+        UI = ui;
203
     }
225
     }
204
 
226
 
205
     /**
227
     /**
210
         sharedKey = sKey;
232
         sharedKey = sKey;
211
     };
233
     };
212
 
234
 
213
-    my.closeAuthenticationWindow = function () {
214
-        if (authenticationWindow) {
215
-            authenticationWindow.close();
216
-            authenticationWindow = null;
217
-        }
218
-    }
219
-
220
     my.authenticateClicked = function () {
235
     my.authenticateClicked = function () {
221
-        // If auth window exists just bring it to the front
222
-        if (authenticationWindow) {
223
-            authenticationWindow.focus();
224
-            return;
225
-        }
236
+        Authentication.focusAuthenticationWindow();
226
         // Get authentication URL
237
         // Get authentication URL
227
-        Moderator.getAuthUrl(function (url) {
238
+        xmpp.getAuthUrl(UI.getRoomName(), function (url) {
228
             // Open popup with authentication URL
239
             // Open popup with authentication URL
229
-            authenticationWindow = messageHandler.openCenteredPopup(
230
-                url, 910, 660,
231
-                // On closed
232
-                function () {
233
-                    // Close authentication dialog if opened
234
-                    if (authDialog) {
235
-                        messageHandler.closeDialog();
236
-                        authDialog = null;
237
-                    }
238
-                    // On popup closed - retry room allocation
239
-                    Moderator.allocateConferenceFocus(roomName, doJoinAfterFocus);
240
-                    authenticationWindow = null;
241
-                });
240
+            var authenticationWindow = Authentication.createAuthenticationWindow(function () {
241
+                // On popup closed - retry room allocation
242
+                xmpp.allocateConferenceFocus(UI.getRoomName(), UI.checkForNicknameAndJoin);
243
+            }, url);
242
             if (!authenticationWindow) {
244
             if (!authenticationWindow) {
243
                 Toolbar.showAuthenticateButton(true);
245
                 Toolbar.showAuthenticateButton(true);
244
                 messageHandler.openMessageDialog(
246
                 messageHandler.openMessageDialog(
279
      */
281
      */
280
     my.openLockDialog = function () {
282
     my.openLockDialog = function () {
281
         // Only the focus is able to set a shared key.
283
         // Only the focus is able to set a shared key.
282
-        if (!Moderator.isModerator()) {
284
+        if (!xmpp.isModerator()) {
283
             if (sharedKey) {
285
             if (sharedKey) {
284
                 messageHandler.openMessageDialog(null,
286
                 messageHandler.openMessageDialog(null,
285
                         "This conversation is currently protected by" +
287
                         "This conversation is currently protected by" +
436
      */
438
      */
437
     my.unlockLockButton = function () {
439
     my.unlockLockButton = function () {
438
         if ($("#lockIcon").hasClass("icon-security-locked"))
440
         if ($("#lockIcon").hasClass("icon-security-locked"))
439
-            buttonClick("#lockIcon", "icon-security icon-security-locked");
441
+            UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
440
     };
442
     };
441
     /**
443
     /**
442
      * Updates the lock button state to locked.
444
      * Updates the lock button state to locked.
443
      */
445
      */
444
     my.lockLockButton = function () {
446
     my.lockLockButton = function () {
445
         if ($("#lockIcon").hasClass("icon-security"))
447
         if ($("#lockIcon").hasClass("icon-security"))
446
-            buttonClick("#lockIcon", "icon-security icon-security-locked");
448
+            UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
447
     };
449
     };
448
 
450
 
449
     /**
451
     /**
486
 
488
 
487
     // Shows or hides SIP calls button
489
     // Shows or hides SIP calls button
488
     my.showSipCallButton = function (show) {
490
     my.showSipCallButton = function (show) {
489
-        if (Moderator.isSipGatewayEnabled() && show) {
491
+        if (xmpp.isSipGatewayEnabled() && show) {
490
             $('#sipCallButton').css({display: "inline"});
492
             $('#sipCallButton').css({display: "inline"});
491
         } else {
493
         } else {
492
             $('#sipCallButton').css({display: "none"});
494
             $('#sipCallButton').css({display: "none"});

+ 1
- 1
modules/UI/toolbars/ToolbarToggler.js View File

67
             toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT;
67
             toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT;
68
         }
68
         }
69
 
69
 
70
-        if (Moderator.isModerator())
70
+        if (xmpp.isModerator())
71
         {
71
         {
72
 //            TODO: Enable settings functionality.
72
 //            TODO: Enable settings functionality.
73
 //                  Need to uncomment the settings button in index.html.
73
 //                  Need to uncomment the settings button in index.html.

+ 7
- 0
modules/UI/util/UIUtil.js View File

11
             = PanelToggler.isVisible() ? PanelToggler.getPanelSize()[0] : 0;
11
             = PanelToggler.isVisible() ? PanelToggler.getPanelSize()[0] : 0;
12
 
12
 
13
         return window.innerWidth - rightPanelWidth;
13
         return window.innerWidth - rightPanelWidth;
14
+    },
15
+    /**
16
+     * Changes the style class of the element given by id.
17
+     */
18
+    buttonClick: function(id, classname) {
19
+        $(id).toggleClass(classname); // add the class to the clicked element
14
     }
20
     }
15
 
21
 
22
+
16
 };
23
 };

+ 194
- 84
modules/UI/videolayout/VideoLayout.js View File

16
     newSrc: ''
16
     newSrc: ''
17
 };
17
 };
18
 
18
 
19
+/**
20
+ * Indicates if we have muted our audio before the conference has started.
21
+ * @type {boolean}
22
+ */
23
+var preMuted = false;
24
+
25
+var mutedAudios = {};
26
+
27
+var flipXLocalVideo = true;
28
+var currentVideoWidth = null;
29
+var currentVideoHeight = null;
30
+
31
+var localVideoSrc = null;
32
+
19
 var defaultLocalDisplayName = "Me";
33
 var defaultLocalDisplayName = "Me";
20
 
34
 
35
+function videoactive( videoelem) {
36
+    if (videoelem.attr('id').indexOf('mixedmslabel') === -1) {
37
+        // ignore mixedmslabela0 and v0
38
+
39
+        videoelem.show();
40
+        VideoLayout.resizeThumbnails();
41
+
42
+        var videoParent = videoelem.parent();
43
+        var parentResourceJid = null;
44
+        if (videoParent)
45
+            parentResourceJid
46
+                = VideoLayout.getPeerContainerResourceJid(videoParent[0]);
47
+
48
+        // Update the large video to the last added video only if there's no
49
+        // current dominant, focused speaker or prezi playing or update it to
50
+        // the current dominant speaker.
51
+        if ((!focusedVideoInfo &&
52
+            !VideoLayout.getDominantSpeakerResourceJid() &&
53
+            !require("../prezi/Prezi").isPresentationVisible()) ||
54
+            (parentResourceJid &&
55
+                VideoLayout.getDominantSpeakerResourceJid() === parentResourceJid)) {
56
+            VideoLayout.updateLargeVideo(
57
+                RTC.getVideoSrc(videoelem[0]),
58
+                1,
59
+                parentResourceJid);
60
+        }
61
+
62
+        VideoLayout.showModeratorIndicator();
63
+    }
64
+}
65
+
66
+function waitForRemoteVideo(selector, ssrc, stream, jid) {
67
+    // XXX(gp) so, every call to this function is *always* preceded by a call
68
+    // to the RTC.attachMediaStream() function but that call is *not* followed
69
+    // by an update to the videoSrcToSsrc map!
70
+    //
71
+    // The above way of doing things results in video SRCs that don't correspond
72
+    // to any SSRC for a short period of time (to be more precise, for as long
73
+    // the waitForRemoteVideo takes to complete). This causes problems (see
74
+    // bellow).
75
+    //
76
+    // I'm wondering why we need to do that; i.e. why call RTC.attachMediaStream()
77
+    // a second time in here and only then update the videoSrcToSsrc map? Why
78
+    // not simply update the videoSrcToSsrc map when the RTC.attachMediaStream()
79
+    // is called the first time? I actually do that in the lastN changed event
80
+    // handler because the "orphan" video SRC is causing troubles there. The
81
+    // purpose of this method would then be to fire the "videoactive.jingle".
82
+    //
83
+    // Food for though I guess :-)
84
+
85
+    if (selector.removed || !selector.parent().is(":visible")) {
86
+        console.warn("Media removed before had started", selector);
87
+        return;
88
+    }
89
+
90
+    if (stream.id === 'mixedmslabel') return;
91
+
92
+    if (selector[0].currentTime > 0) {
93
+        var videoStream = simulcast.getReceivingVideoStream(stream);
94
+        RTC.attachMediaStream(selector, videoStream); // FIXME: why do i have to do this for FF?
95
+
96
+        // FIXME: add a class that will associate peer Jid, video.src, it's ssrc and video type
97
+        //        in order to get rid of too many maps
98
+        if (ssrc && jid) {
99
+            jid2Ssrc[Strophe.getResourceFromJid(jid)] = ssrc;
100
+        } else {
101
+            console.warn("No ssrc given for jid", jid);
102
+        }
103
+
104
+        videoactive(selector);
105
+    } else {
106
+        setTimeout(function () {
107
+            waitForRemoteVideo(selector, ssrc, stream, jid);
108
+        }, 250);
109
+    }
110
+}
111
+
21
 /**
112
 /**
22
  * Returns an array of the video horizontal and vertical indents,
113
  * Returns an array of the video horizontal and vertical indents,
23
  * so that if fits its parent.
114
  * so that if fits its parent.
194
     if (!resourceJid)
285
     if (!resourceJid)
195
         return null;
286
         return null;
196
 
287
 
197
-    if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid))
288
+    if (resourceJid === xmpp.myResource())
198
         return $("#localVideoContainer");
289
         return $("#localVideoContainer");
199
     else
290
     else
200
         return $("#participant_" + resourceJid);
291
         return $("#participant_" + resourceJid);
270
             event.preventDefault();
361
             event.preventDefault();
271
         }
362
         }
272
         var isMute = mutedAudios[jid] == true;
363
         var isMute = mutedAudios[jid] == true;
273
-        connection.moderate.setMute(jid, !isMute);
364
+        xmpp.setMute(jid, !isMute);
365
+
274
         popupmenuElement.setAttribute('style', 'display:none;');
366
         popupmenuElement.setAttribute('style', 'display:none;');
275
 
367
 
276
         if (isMute) {
368
         if (isMute) {
292
     var ejectLinkItem = document.createElement('a');
384
     var ejectLinkItem = document.createElement('a');
293
     ejectLinkItem.innerHTML = ejectIndicator + ' Kick out';
385
     ejectLinkItem.innerHTML = ejectIndicator + ' Kick out';
294
     ejectLinkItem.onclick = function(){
386
     ejectLinkItem.onclick = function(){
295
-        connection.moderate.eject(jid);
387
+        xmpp.eject(jid);
296
         popupmenuElement.setAttribute('style', 'display:none;');
388
         popupmenuElement.setAttribute('style', 'display:none;');
297
     };
389
     };
298
 
390
 
400
 }
492
 }
401
 
493
 
402
 
494
 
495
+/**
496
+ * Checks if video identified by given src is desktop stream.
497
+ * @param videoSrc eg.
498
+ * blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395
499
+ * @returns {boolean}
500
+ */
501
+function isVideoSrcDesktop(jid) {
502
+    // FIXME: fix this mapping mess...
503
+    // figure out if large video is desktop stream or just a camera
504
+
505
+    if(!jid)
506
+        return false;
507
+    var isDesktop = false;
508
+    if (xmpp.myJid() &&
509
+        xmpp.myResource() === jid) {
510
+        // local video
511
+        isDesktop = desktopsharing.isUsingScreenStream();
512
+    } else {
513
+        // Do we have associations...
514
+        var videoSsrc = jid2Ssrc[jid];
515
+        if (videoSsrc) {
516
+            var videoType = ssrc2videoType[videoSsrc];
517
+            if (videoType) {
518
+                // Finally there...
519
+                isDesktop = videoType === 'screen';
520
+            } else {
521
+                console.error("No video type for ssrc: " + videoSsrc);
522
+            }
523
+        } else {
524
+            console.error("No ssrc for jid: " + jid);
525
+        }
526
+    }
527
+    return isDesktop;
528
+}
529
+
530
+
531
+
403
 var VideoLayout = (function (my) {
532
 var VideoLayout = (function (my) {
404
     my.connectionIndicators = {};
533
     my.connectionIndicators = {};
405
 
534
 
407
     my.getVideoSize = getCameraVideoSize;
536
     my.getVideoSize = getCameraVideoSize;
408
     my.getVideoPosition = getCameraVideoPosition;
537
     my.getVideoPosition = getCameraVideoPosition;
409
 
538
 
539
+    my.init = function () {
540
+        // Listen for large video size updates
541
+        document.getElementById('largeVideo')
542
+            .addEventListener('loadedmetadata', function (e) {
543
+                currentVideoWidth = this.videoWidth;
544
+                currentVideoHeight = this.videoHeight;
545
+                VideoLayout.positionLarge(currentVideoWidth, currentVideoHeight);
546
+            });
547
+    };
548
+
410
     my.isInLastN = function(resource) {
549
     my.isInLastN = function(resource) {
411
         return lastNCount < 0 // lastN is disabled, return true
550
         return lastNCount < 0 // lastN is disabled, return true
412
             || (lastNCount > 0 && lastNEndpointsCache.length == 0) // lastNEndpoints cache not built yet, return true
551
             || (lastNCount > 0 && lastNEndpointsCache.length == 0) // lastNEndpoints cache not built yet, return true
422
         document.getElementById('localAudio').autoplay = true;
561
         document.getElementById('localAudio').autoplay = true;
423
         document.getElementById('localAudio').volume = 0;
562
         document.getElementById('localAudio').volume = 0;
424
         if (preMuted) {
563
         if (preMuted) {
425
-            setAudioMuted(true);
564
+            if(!UI.setAudioMuted(true))
565
+            {
566
+                preMuted = mute;
567
+            }
426
             preMuted = false;
568
             preMuted = false;
427
         }
569
         }
428
     };
570
     };
459
             VideoLayout.handleVideoThumbClicked(
601
             VideoLayout.handleVideoThumbClicked(
460
                 RTC.getVideoSrc(localVideo),
602
                 RTC.getVideoSrc(localVideo),
461
                 false,
603
                 false,
462
-                Strophe.getResourceFromJid(connection.emuc.myroomjid));
604
+                xmpp.myResource());
463
         });
605
         });
464
         $('#localVideoContainer').click(function (event) {
606
         $('#localVideoContainer').click(function (event) {
465
             event.stopPropagation();
607
             event.stopPropagation();
466
             VideoLayout.handleVideoThumbClicked(
608
             VideoLayout.handleVideoThumbClicked(
467
                 RTC.getVideoSrc(localVideo),
609
                 RTC.getVideoSrc(localVideo),
468
                 false,
610
                 false,
469
-                Strophe.getResourceFromJid(connection.emuc.myroomjid));
611
+                xmpp.myResource());
470
         });
612
         });
471
 
613
 
472
         // Add hover handler
614
         // Add hover handler
496
 
638
 
497
         localVideoSrc = RTC.getVideoSrc(localVideo);
639
         localVideoSrc = RTC.getVideoSrc(localVideo);
498
 
640
 
499
-        var myResourceJid = null;
500
-        if(connection.emuc.myroomjid)
501
-        {
502
-           myResourceJid = Strophe.getResourceFromJid(connection.emuc.myroomjid);
503
-        }
641
+        var myResourceJid = xmpp.myResource();
642
+
504
         VideoLayout.updateLargeVideo(localVideoSrc, 0,
643
         VideoLayout.updateLargeVideo(localVideoSrc, 0,
505
             myResourceJid);
644
             myResourceJid);
506
 
645
 
539
                 {
678
                 {
540
                     if(container.id == "localVideoWrapper")
679
                     if(container.id == "localVideoWrapper")
541
                     {
680
                     {
542
-                        jid = Strophe.getResourceFromJid(connection.emuc.myroomjid);
681
+                        jid = xmpp.myResource();
543
                     }
682
                     }
544
                     else
683
                     else
545
                     {
684
                     {
617
             largeVideoState.isVisible = $('#largeVideo').is(':visible');
756
             largeVideoState.isVisible = $('#largeVideo').is(':visible');
618
             largeVideoState.isDesktop = isVideoSrcDesktop(resourceJid);
757
             largeVideoState.isDesktop = isVideoSrcDesktop(resourceJid);
619
             if(jid2Ssrc[largeVideoState.userResourceJid] ||
758
             if(jid2Ssrc[largeVideoState.userResourceJid] ||
620
-                (connection && connection.emuc.myroomjid &&
759
+                (xmpp.myResource() &&
621
                     largeVideoState.userResourceJid ===
760
                     largeVideoState.userResourceJid ===
622
-                    Strophe.getResourceFromJid(connection.emuc.myroomjid))) {
761
+                    xmpp.myResource())) {
623
                 largeVideoState.oldResourceJid = largeVideoState.userResourceJid;
762
                 largeVideoState.oldResourceJid = largeVideoState.userResourceJid;
624
             } else {
763
             } else {
625
                 largeVideoState.oldResourceJid = null;
764
                 largeVideoState.oldResourceJid = null;
643
                 var doUpdate = function () {
782
                 var doUpdate = function () {
644
 
783
 
645
                     Avatar.updateActiveSpeakerAvatarSrc(
784
                     Avatar.updateActiveSpeakerAvatarSrc(
646
-                        connection.emuc.findJidFromResource(
785
+                        xmpp.findJidFromResource(
647
                             largeVideoState.userResourceJid));
786
                             largeVideoState.userResourceJid));
648
 
787
 
649
                     if (!userChanged && largeVideoState.preload &&
788
                     if (!userChanged && largeVideoState.preload &&
723
 
862
 
724
                     if(userChanged) {
863
                     if(userChanged) {
725
                         Avatar.showUserAvatar(
864
                         Avatar.showUserAvatar(
726
-                            connection.emuc.findJidFromResource(
865
+                            xmpp.findJidFromResource(
727
                                 largeVideoState.oldResourceJid));
866
                                 largeVideoState.oldResourceJid));
728
                     }
867
                     }
729
 
868
 
738
             }
877
             }
739
         } else {
878
         } else {
740
             Avatar.showUserAvatar(
879
             Avatar.showUserAvatar(
741
-                connection.emuc.findJidFromResource(
880
+                xmpp.findJidFromResource(
742
                     largeVideoState.userResourceJid));
881
                     largeVideoState.userResourceJid));
743
         }
882
         }
744
 
883
 
877
                 focusedVideoInfo = null;
1016
                 focusedVideoInfo = null;
878
                 if(focusResourceJid) {
1017
                 if(focusResourceJid) {
879
                     Avatar.showUserAvatar(
1018
                     Avatar.showUserAvatar(
880
-                        connection.emuc.findJidFromResource(focusResourceJid));
1019
+                        xmpp.findJidFromResource(focusResourceJid));
881
                 }
1020
                 }
882
             }
1021
             }
883
         }
1022
         }
949
 
1088
 
950
         // If the peerJid is null then this video span couldn't be directly
1089
         // If the peerJid is null then this video span couldn't be directly
951
         // associated with a participant (this could happen in the case of prezi).
1090
         // associated with a participant (this could happen in the case of prezi).
952
-        if (Moderator.isModerator() && peerJid !== null)
1091
+        if (xmpp.isModerator() && peerJid !== null)
953
             addRemoteVideoMenu(peerJid, container);
1092
             addRemoteVideoMenu(peerJid, container);
954
 
1093
 
955
         remotes.appendChild(container);
1094
         remotes.appendChild(container);
1134
             if (state == 'show')
1273
             if (state == 'show')
1135
             {
1274
             {
1136
                 // peerContainer.css('-webkit-filter', '');
1275
                 // peerContainer.css('-webkit-filter', '');
1137
-                var jid = connection.emuc.findJidFromResource(resourceJid);
1276
+                var jid = xmpp.findJidFromResource(resourceJid);
1138
                 Avatar.showUserAvatar(jid, false);
1277
                 Avatar.showUserAvatar(jid, false);
1139
             }
1278
             }
1140
             else // if (state == 'avatar')
1279
             else // if (state == 'avatar')
1141
             {
1280
             {
1142
                 // peerContainer.css('-webkit-filter', 'grayscale(100%)');
1281
                 // peerContainer.css('-webkit-filter', 'grayscale(100%)');
1143
-                var jid = connection.emuc.findJidFromResource(resourceJid);
1282
+                var jid = xmpp.findJidFromResource(resourceJid);
1144
                 Avatar.showUserAvatar(jid, true);
1283
                 Avatar.showUserAvatar(jid, true);
1145
             }
1284
             }
1146
         }
1285
         }
1166
         if (name && nickname !== name) {
1305
         if (name && nickname !== name) {
1167
             nickname = name;
1306
             nickname = name;
1168
             window.localStorage.displayname = nickname;
1307
             window.localStorage.displayname = nickname;
1169
-            connection.emuc.addDisplayNameToPresence(nickname);
1170
-            connection.emuc.sendPresence();
1308
+            xmpp.addToPresence("displayName", nickname);
1171
 
1309
 
1172
             Chat.setChatConversationMode(true);
1310
             Chat.setChatConversationMode(true);
1173
         }
1311
         }
1238
      */
1376
      */
1239
     my.showModeratorIndicator = function () {
1377
     my.showModeratorIndicator = function () {
1240
 
1378
 
1241
-        var isModerator = Moderator.isModerator();
1379
+        var isModerator = xmpp.isModerator();
1242
         if (isModerator) {
1380
         if (isModerator) {
1243
             var indicatorSpan = $('#localVideoContainer .focusindicator');
1381
             var indicatorSpan = $('#localVideoContainer .focusindicator');
1244
 
1382
 
1247
                 createModeratorIndicatorElement(indicatorSpan[0]);
1385
                 createModeratorIndicatorElement(indicatorSpan[0]);
1248
             }
1386
             }
1249
         }
1387
         }
1250
-        Object.keys(connection.emuc.members).forEach(function (jid) {
1388
+
1389
+        var members = xmpp.getMembers();
1390
+
1391
+        Object.keys(members).forEach(function (jid) {
1251
 
1392
 
1252
             if (Strophe.getResourceFromJid(jid) === 'focus') {
1393
             if (Strophe.getResourceFromJid(jid) === 'focus') {
1253
                 // Skip server side focus
1394
                 // Skip server side focus
1263
                 return;
1404
                 return;
1264
             }
1405
             }
1265
 
1406
 
1266
-            var member = connection.emuc.members[jid];
1407
+            var member = members[jid];
1267
 
1408
 
1268
             if (member.role === 'moderator') {
1409
             if (member.role === 'moderator') {
1269
                 // Remove menu if peer is moderator
1410
                 // Remove menu if peer is moderator
1435
         var videoSpanId = null;
1576
         var videoSpanId = null;
1436
         var videoContainerId = null;
1577
         var videoContainerId = null;
1437
         if (resourceJid
1578
         if (resourceJid
1438
-                === Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
1579
+                === xmpp.myResource()) {
1439
             videoSpanId = 'localVideoWrapper';
1580
             videoSpanId = 'localVideoWrapper';
1440
             videoContainerId = 'localVideoContainer';
1581
             videoContainerId = 'localVideoContainer';
1441
         }
1582
         }
1478
             }
1619
             }
1479
 
1620
 
1480
             Avatar.showUserAvatar(
1621
             Avatar.showUserAvatar(
1481
-                connection.emuc.findJidFromResource(resourceJid));
1622
+                xmpp.findJidFromResource(resourceJid));
1482
         }
1623
         }
1483
     };
1624
     };
1484
 
1625
 
1603
                     lastNPickupJid = jid;
1744
                     lastNPickupJid = jid;
1604
                     $(document).trigger("pinnedendpointchanged", [jid]);
1745
                     $(document).trigger("pinnedendpointchanged", [jid]);
1605
                 }
1746
                 }
1606
-            } else if (jid == connection.emuc.myroomjid) {
1747
+            } else if (jid == xmpp.myJid()) {
1607
                 $("#localVideoContainer").click();
1748
                 $("#localVideoContainer").click();
1608
             }
1749
             }
1609
         }
1750
         }
1615
     $(document).bind('audiomuted.muc', function (event, jid, isMuted) {
1756
     $(document).bind('audiomuted.muc', function (event, jid, isMuted) {
1616
         /*
1757
         /*
1617
          // FIXME: but focus can not mute in this case ? - check
1758
          // FIXME: but focus can not mute in this case ? - check
1618
-        if (jid === connection.emuc.myroomjid) {
1759
+        if (jid === xmpp.myJid()) {
1619
 
1760
 
1620
             // The local mute indicator is controlled locally
1761
             // The local mute indicator is controlled locally
1621
             return;
1762
             return;
1622
         }*/
1763
         }*/
1623
         var videoSpanId = null;
1764
         var videoSpanId = null;
1624
-        if (jid === connection.emuc.myroomjid) {
1765
+        if (jid === xmpp.myJid()) {
1625
             videoSpanId = 'localVideoContainer';
1766
             videoSpanId = 'localVideoContainer';
1626
         } else {
1767
         } else {
1627
             VideoLayout.ensurePeerContainerExists(jid);
1768
             VideoLayout.ensurePeerContainerExists(jid);
1630
 
1771
 
1631
         mutedAudios[jid] = isMuted;
1772
         mutedAudios[jid] = isMuted;
1632
 
1773
 
1633
-        if (Moderator.isModerator()) {
1774
+        if (xmpp.isModerator()) {
1634
             VideoLayout.updateRemoteVideoMenu(jid, isMuted);
1775
             VideoLayout.updateRemoteVideoMenu(jid, isMuted);
1635
         }
1776
         }
1636
 
1777
 
1648
 
1789
 
1649
         Avatar.showUserAvatar(jid, isMuted);
1790
         Avatar.showUserAvatar(jid, isMuted);
1650
         var videoSpanId = null;
1791
         var videoSpanId = null;
1651
-        if (jid === connection.emuc.myroomjid) {
1792
+        if (jid === xmpp.myJid()) {
1652
             videoSpanId = 'localVideoContainer';
1793
             videoSpanId = 'localVideoContainer';
1653
         } else {
1794
         } else {
1654
             VideoLayout.ensurePeerContainerExists(jid);
1795
             VideoLayout.ensurePeerContainerExists(jid);
1662
     /**
1803
     /**
1663
      * Display name changed.
1804
      * Display name changed.
1664
      */
1805
      */
1665
-    $(document).bind('displaynamechanged',
1666
-                    function (event, jid, displayName, status) {
1806
+    my.onDisplayNameChanged =
1807
+                    function (jid, displayName, status) {
1667
         var name = null;
1808
         var name = null;
1668
         if (jid === 'localVideoContainer'
1809
         if (jid === 'localVideoContainer'
1669
-            || jid === connection.emuc.myroomjid) {
1810
+            || jid === xmpp.myJid()) {
1670
             name = nickname;
1811
             name = nickname;
1671
             setDisplayName('localVideoContainer',
1812
             setDisplayName('localVideoContainer',
1672
                            displayName);
1813
                            displayName);
1680
         }
1821
         }
1681
 
1822
 
1682
         if(jid === 'localVideoContainer')
1823
         if(jid === 'localVideoContainer')
1683
-            jid = connection.emuc.myroomjid;
1824
+            jid = xmpp.myJid();
1684
         if(!name || name != displayName)
1825
         if(!name || name != displayName)
1685
             API.triggerEvent("displayNameChange",{jid: jid, displayname: displayName});
1826
             API.triggerEvent("displayNameChange",{jid: jid, displayname: displayName});
1686
-    });
1827
+    };
1687
 
1828
 
1688
     /**
1829
     /**
1689
      * On dominant speaker changed event.
1830
      * On dominant speaker changed event.
1691
     $(document).bind('dominantspeakerchanged', function (event, resourceJid) {
1832
     $(document).bind('dominantspeakerchanged', function (event, resourceJid) {
1692
         // We ignore local user events.
1833
         // We ignore local user events.
1693
         if (resourceJid
1834
         if (resourceJid
1694
-                === Strophe.getResourceFromJid(connection.emuc.myroomjid))
1835
+                === xmpp.myResource())
1695
             return;
1836
             return;
1696
 
1837
 
1697
         // Update the current dominant speaker.
1838
         // Update the current dominant speaker.
1822
                 if (!isVisible) {
1963
                 if (!isVisible) {
1823
                     console.log("Add to last N", resourceJid);
1964
                     console.log("Add to last N", resourceJid);
1824
 
1965
 
1825
-                    var jid = connection.emuc.findJidFromResource(resourceJid);
1966
+                    var jid = xmpp.findJidFromResource(resourceJid);
1826
                     var mediaStream = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
1967
                     var mediaStream = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
1827
                     var sel = $('#participant_' + resourceJid + '>video');
1968
                     var sel = $('#participant_' + resourceJid + '>video');
1828
 
1969
 
1855
 
1996
 
1856
             var resource, container, src;
1997
             var resource, container, src;
1857
             var myResource
1998
             var myResource
1858
-                = Strophe.getResourceFromJid(connection.emuc.myroomjid);
1999
+                = xmpp.myResource();
1859
 
2000
 
1860
             // Find out which endpoint to show in the large video.
2001
             // Find out which endpoint to show in the large video.
1861
             for (var i = 0; i < lastNEndpoints.length; i++) {
2002
             for (var i = 0; i < lastNEndpoints.length; i++) {
1879
         }
2020
         }
1880
     });
2021
     });
1881
 
2022
 
1882
-    $(document).bind('videoactive.jingle', function (event, videoelem) {
1883
-        if (videoelem.attr('id').indexOf('mixedmslabel') === -1) {
1884
-            // ignore mixedmslabela0 and v0
1885
-
1886
-            videoelem.show();
1887
-            VideoLayout.resizeThumbnails();
1888
-
1889
-            var videoParent = videoelem.parent();
1890
-            var parentResourceJid = null;
1891
-            if (videoParent)
1892
-                parentResourceJid
1893
-                    = VideoLayout.getPeerContainerResourceJid(videoParent[0]);
1894
-
1895
-            // Update the large video to the last added video only if there's no
1896
-            // current dominant, focused speaker or prezi playing or update it to
1897
-            // the current dominant speaker.
1898
-            if ((!focusedVideoInfo &&
1899
-                !VideoLayout.getDominantSpeakerResourceJid() &&
1900
-                !require("../prezi/Prezi").isPresentationVisible()) ||
1901
-                (parentResourceJid &&
1902
-                VideoLayout.getDominantSpeakerResourceJid() === parentResourceJid)) {
1903
-                VideoLayout.updateLargeVideo(
1904
-                    RTC.getVideoSrc(videoelem[0]),
1905
-                    1,
1906
-                    parentResourceJid);
1907
-            }
1908
-
1909
-            VideoLayout.showModeratorIndicator();
1910
-        }
1911
-    });
1912
-
1913
     $(document).bind('simulcastlayerschanging', function (event, endpointSimulcastLayers) {
2023
     $(document).bind('simulcastlayerschanging', function (event, endpointSimulcastLayers) {
1914
         endpointSimulcastLayers.forEach(function (esl) {
2024
         endpointSimulcastLayers.forEach(function (esl) {
1915
 
2025
 
1930
 
2040
 
1931
             // Get session and stream from primary ssrc.
2041
             // Get session and stream from primary ssrc.
1932
             var res = simulcast.getReceivingVideoStreamBySSRC(primarySSRC);
2042
             var res = simulcast.getReceivingVideoStreamBySSRC(primarySSRC);
1933
-            var session = res.session;
2043
+            var sid = res.sid;
1934
             var electedStream = res.stream;
2044
             var electedStream = res.stream;
1935
 
2045
 
1936
-            if (session && electedStream) {
2046
+            if (sid && electedStream) {
1937
                 var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC);
2047
                 var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC);
1938
 
2048
 
1939
-                console.info([esl, primarySSRC, msid, session, electedStream]);
2049
+                console.info([esl, primarySSRC, msid, sid, electedStream]);
1940
 
2050
 
1941
                 var msidParts = msid.split(' ');
2051
                 var msidParts = msid.split(' ');
1942
 
2052
 
1956
                 }
2066
                 }
1957
 
2067
 
1958
             } else {
2068
             } else {
1959
-                console.error('Could not find a stream or a session.', session, electedStream);
2069
+                console.error('Could not find a stream or a session.', sid, electedStream);
1960
             }
2070
             }
1961
         });
2071
         });
1962
     });
2072
     });
1988
 
2098
 
1989
             // Get session and stream from primary ssrc.
2099
             // Get session and stream from primary ssrc.
1990
             var res = simulcast.getReceivingVideoStreamBySSRC(primarySSRC);
2100
             var res = simulcast.getReceivingVideoStreamBySSRC(primarySSRC);
1991
-            var session = res.session;
2101
+            var sid = res.sid;
1992
             var electedStream = res.stream;
2102
             var electedStream = res.stream;
1993
 
2103
 
1994
-            if (session && electedStream) {
2104
+            if (sid && electedStream) {
1995
                 var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC);
2105
                 var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC);
1996
 
2106
 
1997
                 console.info('Switching simulcast substream.');
2107
                 console.info('Switching simulcast substream.');
1998
-                console.info([esl, primarySSRC, msid, session, electedStream]);
2108
+                console.info([esl, primarySSRC, msid, sid, electedStream]);
1999
 
2109
 
2000
                 var msidParts = msid.split(' ');
2110
                 var msidParts = msid.split(' ');
2001
-                var selRemoteVideo = $(['#', 'remoteVideo_', session.sid, '_', msidParts[0]].join(''));
2111
+                var selRemoteVideo = $(['#', 'remoteVideo_', sid, '_', msidParts[0]].join(''));
2002
 
2112
 
2003
                 var updateLargeVideo = (Strophe.getResourceFromJid(ssrc2jid[primarySSRC])
2113
                 var updateLargeVideo = (Strophe.getResourceFromJid(ssrc2jid[primarySSRC])
2004
                     == largeVideoState.userResourceJid);
2114
                     == largeVideoState.userResourceJid);
2035
                 }
2145
                 }
2036
 
2146
 
2037
                 var videoId;
2147
                 var videoId;
2038
-                if(resource == Strophe.getResourceFromJid(connection.emuc.myroomjid))
2148
+                if(resource == xmpp.myResource())
2039
                 {
2149
                 {
2040
                     videoId = "localVideoContainer";
2150
                     videoId = "localVideoContainer";
2041
                 }
2151
                 }
2048
                     connectionIndicator.updatePopoverData();
2158
                     connectionIndicator.updatePopoverData();
2049
 
2159
 
2050
             } else {
2160
             } else {
2051
-                console.error('Could not find a stream or a session.', session, electedStream);
2161
+                console.error('Could not find a stream or a sid.', sid, electedStream);
2052
             }
2162
             }
2053
         });
2163
         });
2054
     });
2164
     });
2063
         if(object.resolution !== null)
2173
         if(object.resolution !== null)
2064
         {
2174
         {
2065
             resolution = object.resolution;
2175
             resolution = object.resolution;
2066
-            object.resolution = resolution[connection.emuc.myroomjid];
2067
-            delete resolution[connection.emuc.myroomjid];
2176
+            object.resolution = resolution[xmpp.myJid()];
2177
+            delete resolution[xmpp.myJid()];
2068
         }
2178
         }
2069
         updateStatsIndicator("localVideoContainer", percent, object);
2179
         updateStatsIndicator("localVideoContainer", percent, object);
2070
         for(var jid in resolution)
2180
         for(var jid in resolution)

+ 1
- 2
modules/connectionquality/connectionquality.js View File

29
  * Sends statistics to other participants
29
  * Sends statistics to other participants
30
  */
30
  */
31
 function sendStats() {
31
 function sendStats() {
32
-    connection.emuc.addConnectionInfoToPresence(convertToMUCStats(stats));
33
-    connection.emuc.sendPresence();
32
+    xmpp.addToPresence("connectionQuality", convertToMUCStats(stats));
34
 }
33
 }
35
 
34
 
36
 /**
35
 /**

+ 1
- 1
modules/desktopsharing/desktopsharing.js View File

1
-/* global $, alert, changeLocalVideo, chrome, config, connection, getConferenceHandler, getUserMediaWithConstraints */
1
+/* global $, alert, changeLocalVideo, chrome, config, getConferenceHandler, getUserMediaWithConstraints */
2
 /**
2
 /**
3
  * Indicates that desktop stream is currently in use(for toggle purpose).
3
  * Indicates that desktop stream is currently in use(for toggle purpose).
4
  * @type {boolean}
4
  * @type {boolean}

+ 32
- 71
modules/simulcast/SimulcastReceiver.js View File

159
 
159
 
160
     // If we haven't receiving a "changed" event yet, then we must be receiving
160
     // If we haven't receiving a "changed" event yet, then we must be receiving
161
     // low quality (that the sender always streams).
161
     // low quality (that the sender always streams).
162
-    if (!ssrc && connection.jingle) {
163
-        var session;
164
-        var i, j, k;
165
-
166
-        var keys = Object.keys(connection.jingle.sessions);
167
-        for (i = 0; i < keys.length; i++) {
168
-            var sid = keys[i];
169
-
170
-            if (ssrc) {
171
-                // stream found, stop.
172
-                break;
173
-            }
174
-
175
-            session = connection.jingle.sessions[sid];
176
-            if (session.remoteStreams) {
177
-                for (j = 0; j < session.remoteStreams.length; j++) {
178
-                    var remoteStream = session.remoteStreams[j];
179
-
180
-                    if (ssrc) {
181
-                        // stream found, stop.
182
-                        break;
183
-                    }
184
-                    var tracks = remoteStream.getVideoTracks();
185
-                    if (tracks) {
186
-                        for (k = 0; k < tracks.length; k++) {
187
-                            var track = tracks[k];
188
-                            var msid = [remoteStream.id, track.id].join(' ');
189
-                            var _ssrc = this._remoteMaps.msid2ssrc[msid];
190
-                            var _jid = ssrc2jid[_ssrc];
191
-                            var quality = this._remoteMaps.msid2Quality[msid];
192
-                            if (jid == _jid && quality == 0) {
193
-                                ssrc = _ssrc;
194
-                                // stream found, stop.
195
-                                break;
196
-                            }
197
-                        }
198
-                    }
162
+    if(!ssrc)
163
+    {
164
+        var remoteStreamObject = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
165
+        var remoteStream = remoteStreamObject.getOriginalStream();
166
+        var tracks = remoteStream.getVideoTracks();
167
+        if (tracks) {
168
+            for (var k = 0; k < tracks.length; k++) {
169
+                var track = tracks[k];
170
+                var msid = [remoteStream.id, track.id].join(' ');
171
+                var _ssrc = this._remoteMaps.msid2ssrc[msid];
172
+                var quality = this._remoteMaps.msid2Quality[msid];
173
+                if (quality == 0) {
174
+                    ssrc = _ssrc;
199
                 }
175
                 }
200
             }
176
             }
201
         }
177
         }
206
 
182
 
207
 SimulcastReceiver.prototype.getReceivingVideoStreamBySSRC = function (ssrc)
183
 SimulcastReceiver.prototype.getReceivingVideoStreamBySSRC = function (ssrc)
208
 {
184
 {
209
-    var session, electedStream;
185
+    var sid, electedStream;
210
     var i, j, k;
186
     var i, j, k;
211
-    if (connection.jingle) {
212
-        var keys = Object.keys(connection.jingle.sessions);
213
-        for (i = 0; i < keys.length; i++) {
214
-            var sid = keys[i];
215
-
216
-            if (electedStream) {
217
-                // stream found, stop.
218
-                break;
219
-            }
220
-
221
-            session = connection.jingle.sessions[sid];
222
-            if (session.remoteStreams) {
223
-                for (j = 0; j < session.remoteStreams.length; j++) {
224
-                    var remoteStream = session.remoteStreams[j];
225
-
226
-                    if (electedStream) {
227
-                        // stream found, stop.
228
-                        break;
229
-                    }
230
-                    var tracks = remoteStream.getVideoTracks();
231
-                    if (tracks) {
232
-                        for (k = 0; k < tracks.length; k++) {
233
-                            var track = tracks[k];
234
-                            var msid = [remoteStream.id, track.id].join(' ');
235
-                            var tmp = this._remoteMaps.msid2ssrc[msid];
236
-                            if (tmp == ssrc) {
237
-                                electedStream = new webkitMediaStream([track]);
238
-                                // stream found, stop.
239
-                                break;
240
-                            }
241
-                        }
242
-                    }
187
+    var jid = ssrc2jid[ssrc];
188
+    if(jid)
189
+    {
190
+        var remoteStreamObject = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
191
+        var remoteStream = remoteStreamObject.getOriginalStream();
192
+        var tracks = remoteStream.getVideoTracks();
193
+        if (tracks) {
194
+            for (k = 0; k < tracks.length; k++) {
195
+                var track = tracks[k];
196
+                var msid = [remoteStream.id, track.id].join(' ');
197
+                var tmp = this._remoteMaps.msid2ssrc[msid];
198
+                if (tmp == ssrc) {
199
+                    electedStream = new webkitMediaStream([track]);
200
+                    sid = remoteStreamObject.sid;
201
+                    // stream found, stop.
202
+                    break;
243
                 }
203
                 }
244
             }
204
             }
245
         }
205
         }
206
+
246
     }
207
     }
247
 
208
 
248
     return {
209
     return {
249
-        session: session,
210
+        sid: sid,
250
         stream: electedStream
211
         stream: electedStream
251
     };
212
     };
252
 };
213
 };

+ 3
- 24
modules/statistics/RTPStatsCollector.js View File

329
 };
329
 };
330
 
330
 
331
 StatsCollector.prototype.logStats = function () {
331
 StatsCollector.prototype.logStats = function () {
332
-    if (!focusMucJid) {
333
-        return;
334
-    }
335
-
336
-    var deflate = true;
337
-
338
-    var content = JSON.stringify(this.statsToBeLogged);
339
-    if (deflate) {
340
-        content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
341
-    }
342
-    content = Base64.encode(content);
343
-
344
-    // XEP-0337-ish
345
-    var message = $msg({to: focusMucJid, type: 'normal'});
346
-    message.c('log', { xmlns: 'urn:xmpp:eventlog',
347
-        id: 'PeerConnectionStats'});
348
-    message.c('message').t(content).up();
349
-    if (deflate) {
350
-        message.c('tag', {name: "deflated", value: "true"}).up();
351
-    }
352
-    message.up();
353
-
354
-    connection.send(message);
355
 
332
 
333
+    if(!xmpp.sendLogs(this.statsToBeLogged))
334
+        return;
356
     // Reset the stats
335
     // Reset the stats
357
     this.statsToBeLogged.stats = {};
336
     this.statsToBeLogged.stats = {};
358
     this.statsToBeLogged.timestamps = [];
337
     this.statsToBeLogged.timestamps = [];
700
             // but it seems to vary between 0 and around 32k.
679
             // but it seems to vary between 0 and around 32k.
701
             audioLevel = audioLevel / 32767;
680
             audioLevel = audioLevel / 32767;
702
             jidStats.setSsrcAudioLevel(ssrc, audioLevel);
681
             jidStats.setSsrcAudioLevel(ssrc, audioLevel);
703
-            if(jid != connection.emuc.myroomjid)
682
+            if(jid != xmpp.myJid())
704
                 this.eventEmitter.emit("statistics.audioLevel", jid, audioLevel);
683
                 this.eventEmitter.emit("statistics.audioLevel", jid, audioLevel);
705
         }
684
         }
706
 
685
 

+ 9
- 8
modules/statistics/statistics.js View File

59
     localStats.start();
59
     localStats.start();
60
 }
60
 }
61
 
61
 
62
+function onDisposeConference(onUnload) {
63
+    stopRemote();
64
+    if(onUnload) {
65
+        stopLocal();
66
+        eventEmitter.removeAllListeners();
67
+    }
68
+}
69
+
62
 
70
 
63
 var statistics =
71
 var statistics =
64
 {
72
 {
117
         startRemoteStats(event.peerconnection);
125
         startRemoteStats(event.peerconnection);
118
     },
126
     },
119
 
127
 
120
-    onDisposeConference: function (onUnload) {
121
-        stopRemote();
122
-        if(onUnload) {
123
-            stopLocal();
124
-            eventEmitter.removeAllListeners();
125
-        }
126
-    },
127
-
128
     start: function () {
128
     start: function () {
129
         this.addConnectionStatsListener(connectionquality.updateLocalStats);
129
         this.addConnectionStatsListener(connectionquality.updateLocalStats);
130
         this.addRemoteStatsStopListener(connectionquality.stopSendingStats);
130
         this.addRemoteStatsStopListener(connectionquality.stopSendingStats);
131
         RTC.addStreamListener(onStreamCreated,
131
         RTC.addStreamListener(onStreamCreated,
132
             StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
132
             StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
133
+        xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
133
     }
134
     }
134
 
135
 
135
 };
136
 };

libs/strophe/strophe.jingle.session.js → modules/xmpp/JingleSession.js View File

1
 /* jshint -W117 */
1
 /* jshint -W117 */
2
+var TraceablePeerConnection = require("./TraceablePeerConnection");
3
+var SDPDiffer = require("./SDPDiffer");
4
+var SDPUtil = require("./SDPUtil");
5
+var SDP = require("./SDP");
6
+
2
 // Jingle stuff
7
 // Jingle stuff
3
-function JingleSession(me, sid, connection) {
8
+function JingleSession(me, sid, connection, service) {
4
     this.me = me;
9
     this.me = me;
5
     this.sid = sid;
10
     this.sid = sid;
6
     this.connection = connection;
11
     this.connection = connection;
12
     this.localSDP = null;
17
     this.localSDP = null;
13
     this.remoteSDP = null;
18
     this.remoteSDP = null;
14
     this.relayedStreams = [];
19
     this.relayedStreams = [];
15
-    this.remoteStreams = [];
16
     this.startTime = null;
20
     this.startTime = null;
17
     this.stopTime = null;
21
     this.stopTime = null;
18
     this.media_constraints = null;
22
     this.media_constraints = null;
19
     this.pc_constraints = null;
23
     this.pc_constraints = null;
20
     this.ice_config = {};
24
     this.ice_config = {};
21
     this.drip_container = [];
25
     this.drip_container = [];
26
+    this.service = service;
22
 
27
 
23
     this.usetrickle = true;
28
     this.usetrickle = true;
24
     this.usepranswer = false; // early transport warmup -- mind you, this might fail. depends on webrtc issue 1718
29
     this.usepranswer = false; // early transport warmup -- mind you, this might fail. depends on webrtc issue 1718
73
         self.sendIceCandidate(event.candidate);
78
         self.sendIceCandidate(event.candidate);
74
     };
79
     };
75
     this.peerconnection.onaddstream = function (event) {
80
     this.peerconnection.onaddstream = function (event) {
76
-        self.remoteStreams.push(event.stream);
77
         console.log("REMOTE STREAM ADDED: " + event.stream + " - " + event.stream.id);
81
         console.log("REMOTE STREAM ADDED: " + event.stream + " - " + event.stream.id);
78
-        $(document).trigger('remotestreamadded.jingle', [event, self.sid]);
82
+        self.remoteStreamAdded(event);
79
     };
83
     };
80
     this.peerconnection.onremovestream = function (event) {
84
     this.peerconnection.onremovestream = function (event) {
81
         // Remove the stream from remoteStreams
85
         // Remove the stream from remoteStreams
82
-        var streamIdx = self.remoteStreams.indexOf(event.stream);
83
-        if(streamIdx !== -1){
84
-            self.remoteStreams.splice(streamIdx, 1);
85
-        }
86
         // FIXME: remotestreamremoved.jingle not defined anywhere(unused)
86
         // FIXME: remotestreamremoved.jingle not defined anywhere(unused)
87
         $(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
87
         $(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
88
     };
88
     };
99
                 this.stopTime = new Date();
99
                 this.stopTime = new Date();
100
                 break;
100
                 break;
101
         }
101
         }
102
-        $(document).trigger('iceconnectionstatechange.jingle', [self.sid, self]);
102
+        onIceConnectionStateChange(self.sid, self);
103
     };
103
     };
104
     // add any local and relayed stream
104
     // add any local and relayed stream
105
     RTC.localStreams.forEach(function(stream) {
105
     RTC.localStreams.forEach(function(stream) {
110
     });
110
     });
111
 };
111
 };
112
 
112
 
113
+function onIceConnectionStateChange(sid, session) {
114
+    switch (session.peerconnection.iceConnectionState) {
115
+        case 'checking':
116
+            session.timeChecking = (new Date()).getTime();
117
+            session.firstconnect = true;
118
+            break;
119
+        case 'completed': // on caller side
120
+        case 'connected':
121
+            if (session.firstconnect) {
122
+                session.firstconnect = false;
123
+                var metadata = {};
124
+                metadata.setupTime
125
+                    = (new Date()).getTime() - session.timeChecking;
126
+                session.peerconnection.getStats(function (res) {
127
+                    if(res && res.result) {
128
+                        res.result().forEach(function (report) {
129
+                            if (report.type == 'googCandidatePair' &&
130
+                                report.stat('googActiveConnection') == 'true') {
131
+                                metadata.localCandidateType
132
+                                    = report.stat('googLocalCandidateType');
133
+                                metadata.remoteCandidateType
134
+                                    = report.stat('googRemoteCandidateType');
135
+
136
+                                // log pair as well so we can get nice pie
137
+                                // charts
138
+                                metadata.candidatePair
139
+                                    = report.stat('googLocalCandidateType') +
140
+                                        ';' +
141
+                                        report.stat('googRemoteCandidateType');
142
+
143
+                                if (report.stat('googRemoteAddress').indexOf('[') === 0)
144
+                                {
145
+                                    metadata.ipv6 = true;
146
+                                }
147
+                            }
148
+                        });
149
+                    }
150
+                });
151
+            }
152
+            break;
153
+    }
154
+}
155
+
113
 JingleSession.prototype.accept = function () {
156
 JingleSession.prototype.accept = function () {
114
     var self = this;
157
     var self = this;
115
     this.state = 'active';
158
     this.state = 'active';
145
         // FIXME: change any inactive to sendrecv or whatever they were originally
188
         // FIXME: change any inactive to sendrecv or whatever they were originally
146
         sdp = sdp.replace('a=inactive', 'a=sendrecv');
189
         sdp = sdp.replace('a=inactive', 'a=sendrecv');
147
     }
190
     }
191
+    var self = this;
148
     this.peerconnection.setLocalDescription(new RTCSessionDescription({type: 'answer', sdp: sdp}),
192
     this.peerconnection.setLocalDescription(new RTCSessionDescription({type: 'answer', sdp: sdp}),
149
         function () {
193
         function () {
150
             //console.log('setLocalDescription success');
194
             //console.log('setLocalDescription success');
151
-            $(document).trigger('setLocalDescription.jingle', [self.sid]);
195
+            self.setLocalDescription();
152
 
196
 
153
-            this.connection.sendIQ(accept,
197
+            self.connection.sendIQ(accept,
154
                 function () {
198
                 function () {
155
                     var ack = {};
199
                     var ack = {};
156
                     ack.source = 'answer';
200
                     ack.source = 'answer';
347
                 action: 'session-initiate',
391
                 action: 'session-initiate',
348
                 initiator: this.initiator,
392
                 initiator: this.initiator,
349
                 sid: this.sid});
393
                 sid: this.sid});
350
-        this.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC);
351
-        this.connection.sendIQ(init,
394
+        self.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC);
395
+        self.connection.sendIQ(init,
352
             function () {
396
             function () {
353
                 var ack = {};
397
                 var ack = {};
354
                 ack.source = 'offer';
398
                 ack.source = 'offer';
369
     sdp.sdp = this.localSDP.raw;
413
     sdp.sdp = this.localSDP.raw;
370
     this.peerconnection.setLocalDescription(sdp,
414
     this.peerconnection.setLocalDescription(sdp,
371
         function () {
415
         function () {
372
-            if(this.usetrickle)
416
+            if(self.usetrickle)
373
             {
417
             {
374
                 sendJingle();
418
                 sendJingle();
375
-                $(document).trigger('setLocalDescription.jingle', [self.sid]);
376
             }
419
             }
377
-            else
378
-                $(document).trigger('setLocalDescription.jingle', [self.sid]);
420
+            self.setLocalDescription();
379
             //console.log('setLocalDescription success');
421
             //console.log('setLocalDescription success');
380
         },
422
         },
381
         function (e) {
423
         function (e) {
587
                 var publicLocalDesc = simulcast.reverseTransformLocalDescription(sdp);
629
                 var publicLocalDesc = simulcast.reverseTransformLocalDescription(sdp);
588
                 var publicLocalSDP = new SDP(publicLocalDesc.sdp);
630
                 var publicLocalSDP = new SDP(publicLocalDesc.sdp);
589
                 publicLocalSDP.toJingle(accept, self.initiator == self.me ? 'initiator' : 'responder', ssrcs);
631
                 publicLocalSDP.toJingle(accept, self.initiator == self.me ? 'initiator' : 'responder', ssrcs);
590
-                this.connection.sendIQ(accept,
632
+                self.connection.sendIQ(accept,
591
                     function () {
633
                     function () {
592
                         var ack = {};
634
                         var ack = {};
593
                         ack.source = 'answer';
635
                         ack.source = 'answer';
610
             //console.log('setLocalDescription success');
652
             //console.log('setLocalDescription success');
611
             if (self.usetrickle && !self.usepranswer) {
653
             if (self.usetrickle && !self.usepranswer) {
612
                 sendJingle();
654
                 sendJingle();
613
-                $(document).trigger('setLocalDescription.jingle', [self.sid]);
614
             }
655
             }
615
-            else
616
-                $(document).trigger('setLocalDescription.jingle', [self.sid]);
656
+            self.setLocalDescription();
617
         },
657
         },
618
         function (e) {
658
         function (e) {
619
             console.error('setLocalDescription failed', e);
659
             console.error('setLocalDescription failed', e);
799
     if (this.peerconnection.signalingState == 'closed') return;
839
     if (this.peerconnection.signalingState == 'closed') return;
800
     if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
840
     if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
801
         // There is nothing to do since scheduled job might have been executed by another succeeding call
841
         // There is nothing to do since scheduled job might have been executed by another succeeding call
802
-        $(document).trigger('setLocalDescription.jingle', [self.sid]);
842
+        this.setLocalDescription();
803
         if(successCallback){
843
         if(successCallback){
804
             successCallback();
844
             successCallback();
805
         }
845
         }
889
                     self.peerconnection.setLocalDescription(modifiedAnswer,
929
                     self.peerconnection.setLocalDescription(modifiedAnswer,
890
                         function() {
930
                         function() {
891
                             //console.log('modified setLocalDescription ok');
931
                             //console.log('modified setLocalDescription ok');
892
-                            $(document).trigger('setLocalDescription.jingle', [self.sid]);
932
+                            self.setLocalDescription();
893
                             if(successCallback){
933
                             if(successCallback){
894
                                 successCallback();
934
                                 successCallback();
895
                             }
935
                             }
1064
     } else if (this.videoMuteByUser) {
1104
     } else if (this.videoMuteByUser) {
1065
         return;
1105
         return;
1066
     }
1106
     }
1107
+
1108
+    var self = this;
1109
+    var localCallback = function (mute) {
1110
+        self.connection.emuc.addVideoInfoToPresence(mute);
1111
+        self.connection.emuc.sendPresence();
1112
+        return callback(mute)
1113
+    };
1114
+
1067
     if (mute == RTC.localVideo.isMuted())
1115
     if (mute == RTC.localVideo.isMuted())
1068
     {
1116
     {
1069
         // Even if no change occurs, the specified callback is to be executed.
1117
         // Even if no change occurs, the specified callback is to be executed.
1070
         // The specified callback may, optionally, return a successCallback
1118
         // The specified callback may, optionally, return a successCallback
1071
         // which is to be executed as well.
1119
         // which is to be executed as well.
1072
-        var successCallback = callback(mute);
1120
+        var successCallback = localCallback(mute);
1073
 
1121
 
1074
         if (successCallback) {
1122
         if (successCallback) {
1075
             successCallback();
1123
             successCallback();
1079
 
1127
 
1080
         this.hardMuteVideo(mute);
1128
         this.hardMuteVideo(mute);
1081
 
1129
 
1082
-        this.modifySources(callback(mute));
1130
+        this.modifySources(localCallback(mute));
1083
     }
1131
     }
1084
 };
1132
 };
1085
 
1133
 
1086
 // SDP-based mute by going recvonly/sendrecv
1134
 // SDP-based mute by going recvonly/sendrecv
1087
 // FIXME: should probably black out the screen as well
1135
 // FIXME: should probably black out the screen as well
1088
 JingleSession.prototype.toggleVideoMute = function (callback) {
1136
 JingleSession.prototype.toggleVideoMute = function (callback) {
1089
-    setVideoMute(RTC.localVideo.isMuted(), callback);
1137
+    this.service.setVideoMute(RTC.localVideo.isMuted(), callback);
1090
 };
1138
 };
1091
 
1139
 
1092
 JingleSession.prototype.hardMuteVideo = function (muted) {
1140
 JingleSession.prototype.hardMuteVideo = function (muted) {
1172
 
1220
 
1173
 JingleSession.onJingleFatalError = function (session, error)
1221
 JingleSession.onJingleFatalError = function (session, error)
1174
 {
1222
 {
1175
-    sessionTerminated = true;
1223
+    this.service.sessionTerminated = true;
1176
     connection.emuc.doLeave();
1224
     connection.emuc.doLeave();
1177
     UI.messageHandler.showError(  "Sorry",
1225
     UI.messageHandler.showError(  "Sorry",
1178
         "Internal application error[setRemoteDescription]");
1226
         "Internal application error[setRemoteDescription]");
1179
-}
1227
+}
1228
+
1229
+JingleSession.prototype.setLocalDescription = function () {
1230
+    // put our ssrcs into presence so other clients can identify our stream
1231
+    var newssrcs = [];
1232
+    var media = simulcast.parseMedia(this.peerconnection.localDescription);
1233
+    media.forEach(function (media) {
1234
+
1235
+        if(Object.keys(media.sources).length > 0) {
1236
+            // TODO(gp) maybe exclude FID streams?
1237
+            Object.keys(media.sources).forEach(function (ssrc) {
1238
+                newssrcs.push({
1239
+                    'ssrc': ssrc,
1240
+                    'type': media.type,
1241
+                    'direction': media.direction
1242
+                });
1243
+            });
1244
+        }
1245
+        else if(this.localStreamsSSRC && this.localStreamsSSRC[media.type])
1246
+        {
1247
+            newssrcs.push({
1248
+                'ssrc': this.localStreamsSSRC[media.type],
1249
+                'type': media.type,
1250
+                'direction': media.direction
1251
+            });
1252
+        }
1253
+
1254
+    });
1255
+
1256
+    console.log('new ssrcs', newssrcs);
1257
+
1258
+    // Have to clear presence map to get rid of removed streams
1259
+    this.connection.emuc.clearPresenceMedia();
1260
+
1261
+    if (newssrcs.length > 0) {
1262
+        for (var i = 1; i <= newssrcs.length; i ++) {
1263
+            // Change video type to screen
1264
+            if (newssrcs[i-1].type === 'video' && desktopsharing.isUsingScreenStream()) {
1265
+                newssrcs[i-1].type = 'screen';
1266
+            }
1267
+            this.connection.emuc.addMediaToPresence(i,
1268
+                newssrcs[i-1].type, newssrcs[i-1].ssrc, newssrcs[i-1].direction);
1269
+        }
1270
+
1271
+        this.connection.emuc.sendPresence();
1272
+    }
1273
+}
1274
+
1275
+// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
1276
+function sendKeyframe(pc) {
1277
+    console.log('sendkeyframe', pc.iceConnectionState);
1278
+    if (pc.iceConnectionState !== 'connected') return; // safe...
1279
+    pc.setRemoteDescription(
1280
+        pc.remoteDescription,
1281
+        function () {
1282
+            pc.createAnswer(
1283
+                function (modifiedAnswer) {
1284
+                    pc.setLocalDescription(
1285
+                        modifiedAnswer,
1286
+                        function () {
1287
+                            // noop
1288
+                        },
1289
+                        function (error) {
1290
+                            console.log('triggerKeyframe setLocalDescription failed', error);
1291
+                            UI.messageHandler.showError();
1292
+                        }
1293
+                    );
1294
+                },
1295
+                function (error) {
1296
+                    console.log('triggerKeyframe createAnswer failed', error);
1297
+                    UI.messageHandler.showError();
1298
+                }
1299
+            );
1300
+        },
1301
+        function (error) {
1302
+            console.log('triggerKeyframe setRemoteDescription failed', error);
1303
+            UI.messageHandler.showError();
1304
+        }
1305
+    );
1306
+}
1307
+
1308
+
1309
+JingleSession.prototype.remoteStreamAdded = function (data) {
1310
+    var self = this;
1311
+    var thessrc;
1312
+
1313
+    // look up an associated JID for a stream id
1314
+    if (data.stream.id && data.stream.id.indexOf('mixedmslabel') === -1) {
1315
+        // look only at a=ssrc: and _not_ at a=ssrc-group: lines
1316
+
1317
+        var ssrclines
1318
+            = SDPUtil.find_lines(this.peerconnection.remoteDescription.sdp, 'a=ssrc:');
1319
+        ssrclines = ssrclines.filter(function (line) {
1320
+            // NOTE(gp) previously we filtered on the mslabel, but that property
1321
+            // is not always present.
1322
+            // return line.indexOf('mslabel:' + data.stream.label) !== -1;
1323
+
1324
+            return ((line.indexOf('msid:' + data.stream.id) !== -1));
1325
+        });
1326
+        if (ssrclines.length) {
1327
+            thessrc = ssrclines[0].substring(7).split(' ')[0];
1328
+
1329
+            // We signal our streams (through Jingle to the focus) before we set
1330
+            // our presence (through which peers associate remote streams to
1331
+            // jids). So, it might arrive that a remote stream is added but
1332
+            // ssrc2jid is not yet updated and thus data.peerjid cannot be
1333
+            // successfully set. Here we wait for up to a second for the
1334
+            // presence to arrive.
1335
+
1336
+            if (!ssrc2jid[thessrc]) {
1337
+                // TODO(gp) limit wait duration to 1 sec.
1338
+                setTimeout(function(d) {
1339
+                    return function() {
1340
+                        self.remoteStreamAdded(d);
1341
+                    }
1342
+                }(data), 250);
1343
+                return;
1344
+            }
1345
+
1346
+            // ok to overwrite the one from focus? might save work in colibri.js
1347
+            console.log('associated jid', ssrc2jid[thessrc], data.peerjid);
1348
+            if (ssrc2jid[thessrc]) {
1349
+                data.peerjid = ssrc2jid[thessrc];
1350
+            }
1351
+        }
1352
+    }
1353
+
1354
+    //TODO: this code should be removed when firefox implement multistream support
1355
+    if(RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_FIREFOX)
1356
+    {
1357
+        if((notReceivedSSRCs.length == 0) ||
1358
+            !ssrc2jid[notReceivedSSRCs[notReceivedSSRCs.length - 1]])
1359
+        {
1360
+            // TODO(gp) limit wait duration to 1 sec.
1361
+            setTimeout(function(d) {
1362
+                return function() {
1363
+                    self.remoteStreamAdded(d);
1364
+                }
1365
+            }(data), 250);
1366
+            return;
1367
+        }
1368
+
1369
+        thessrc = notReceivedSSRCs.pop();
1370
+        if (ssrc2jid[thessrc]) {
1371
+            data.peerjid = ssrc2jid[thessrc];
1372
+        }
1373
+    }
1374
+
1375
+    RTC.createRemoteStream(data, this.sid, thessrc);
1376
+
1377
+    var isVideo = data.stream.getVideoTracks().length > 0;
1378
+    // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
1379
+    if (isVideo &&
1380
+        data.peerjid && this.peerjid === data.peerjid &&
1381
+        data.stream.getVideoTracks().length === 0 &&
1382
+        RTC.localVideo.getTracks().length > 0) {
1383
+        window.setTimeout(function () {
1384
+            sendKeyframe(self.peerconnection);
1385
+        }, 3000);
1386
+    }
1387
+}
1388
+
1389
+module.exports = JingleSession;

libs/strophe/strophe.jingle.sdp.js → modules/xmpp/SDP.js View File

1
 /* jshint -W117 */
1
 /* jshint -W117 */
2
+var SDPUtil = require("./SDPUtil");
3
+
2
 // SDP STUFF
4
 // SDP STUFF
3
 function SDP(sdp) {
5
 function SDP(sdp) {
4
     this.media = sdp.split('\r\nm=');
6
     this.media = sdp.split('\r\nm=');
71
     return contains;
73
     return contains;
72
 };
74
 };
73
 
75
 
74
-function SDPDiffer(mySDP, otherSDP) {
75
-    this.mySDP = mySDP;
76
-    this.otherSDP = otherSDP;
77
-}
78
-
79
-/**
80
- * Returns map of MediaChannel that contains only media not contained in <tt>otherSdp</tt>. Mapped by channel idx.
81
- * @param otherSdp the other SDP to check ssrc with.
82
- */
83
-SDPDiffer.prototype.getNewMedia = function() {
84
-
85
-    // this could be useful in Array.prototype.
86
-    function arrayEquals(array) {
87
-        // if the other array is a falsy value, return
88
-        if (!array)
89
-            return false;
90
-
91
-        // compare lengths - can save a lot of time
92
-        if (this.length != array.length)
93
-            return false;
94
-
95
-        for (var i = 0, l=this.length; i < l; i++) {
96
-            // Check if we have nested arrays
97
-            if (this[i] instanceof Array && array[i] instanceof Array) {
98
-                // recurse into the nested arrays
99
-                if (!this[i].equals(array[i]))
100
-                    return false;
101
-            }
102
-            else if (this[i] != array[i]) {
103
-                // Warning - two different object instances will never be equal: {x:20} != {x:20}
104
-                return false;
105
-            }
106
-        }
107
-        return true;
108
-    }
109
-
110
-    var myMedias = this.mySDP.getMediaSsrcMap();
111
-    var othersMedias = this.otherSDP.getMediaSsrcMap();
112
-    var newMedia = {};
113
-    Object.keys(othersMedias).forEach(function(othersMediaIdx) {
114
-        var myMedia = myMedias[othersMediaIdx];
115
-        var othersMedia = othersMedias[othersMediaIdx];
116
-        if(!myMedia && othersMedia) {
117
-            // Add whole channel
118
-            newMedia[othersMediaIdx] = othersMedia;
119
-            return;
120
-        }
121
-        // Look for new ssrcs accross the channel
122
-        Object.keys(othersMedia.ssrcs).forEach(function(ssrc) {
123
-            if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) {
124
-                // Allocate channel if we've found ssrc that doesn't exist in our channel
125
-                if(!newMedia[othersMediaIdx]){
126
-                    newMedia[othersMediaIdx] = {
127
-                        mediaindex: othersMedia.mediaindex,
128
-                        mid: othersMedia.mid,
129
-                        ssrcs: {},
130
-                        ssrcGroups: []
131
-                    };
132
-                }
133
-                newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc];
134
-            }
135
-        });
136
-
137
-        // Look for new ssrc groups across the channels
138
-        othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){
139
-
140
-            // try to match the other ssrc-group with an ssrc-group of ours
141
-            var matched = false;
142
-            for (var i = 0; i < myMedia.ssrcGroups.length; i++) {
143
-                var mySsrcGroup = myMedia.ssrcGroups[i];
144
-                if (otherSsrcGroup.semantics == mySsrcGroup.semantics
145
-                    && arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) {
146
-
147
-                    matched = true;
148
-                    break;
149
-                }
150
-            }
151
-
152
-            if (!matched) {
153
-                // Allocate channel if we've found an ssrc-group that doesn't
154
-                // exist in our channel
155
-
156
-                if(!newMedia[othersMediaIdx]){
157
-                    newMedia[othersMediaIdx] = {
158
-                        mediaindex: othersMedia.mediaindex,
159
-                        mid: othersMedia.mid,
160
-                        ssrcs: {},
161
-                        ssrcGroups: []
162
-                    };
163
-                }
164
-                newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup);
165
-            }
166
-        });
167
-    });
168
-    return newMedia;
169
-};
170
-
171
-/**
172
- * Sends SSRC update IQ.
173
- * @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
174
- * @param sid session identifier that will be put into the IQ.
175
- * @param initiator initiator identifier.
176
- * @param toJid destination Jid
177
- * @param isAdd indicates if this is remove or add operation.
178
- */
179
-SDPDiffer.prototype.toJingle = function(modify) {
180
-    var sdpMediaSsrcs = this.getNewMedia();
181
-    var self = this;
182
-
183
-    // FIXME: only announce video ssrcs since we mix audio and dont need
184
-    //      the audio ssrcs therefore
185
-    var modified = false;
186
-    Object.keys(sdpMediaSsrcs).forEach(function(mediaindex){
187
-        modified = true;
188
-        var media = sdpMediaSsrcs[mediaindex];
189
-        modify.c('content', {name: media.mid});
190
-
191
-        modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: media.mid});
192
-        // FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
193
-        // generate sources from lines
194
-        Object.keys(media.ssrcs).forEach(function(ssrcNum) {
195
-            var mediaSsrc = media.ssrcs[ssrcNum];
196
-            modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
197
-            modify.attrs({ssrc: mediaSsrc.ssrc});
198
-            // iterate over ssrc lines
199
-            mediaSsrc.lines.forEach(function (line) {
200
-                var idx = line.indexOf(' ');
201
-                var kv = line.substr(idx + 1);
202
-                modify.c('parameter');
203
-                if (kv.indexOf(':') == -1) {
204
-                    modify.attrs({ name: kv });
205
-                } else {
206
-                    modify.attrs({ name: kv.split(':', 2)[0] });
207
-                    modify.attrs({ value: kv.split(':', 2)[1] });
208
-                }
209
-                modify.up(); // end of parameter
210
-            });
211
-            modify.up(); // end of source
212
-        });
213
-
214
-        // generate source groups from lines
215
-        media.ssrcGroups.forEach(function(ssrcGroup) {
216
-            if (ssrcGroup.ssrcs.length != 0) {
217
-
218
-                modify.c('ssrc-group', {
219
-                    semantics: ssrcGroup.semantics,
220
-                    xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
221
-                });
222
-
223
-                ssrcGroup.ssrcs.forEach(function (ssrc) {
224
-                    modify.c('source', { ssrc: ssrc })
225
-                        .up(); // end of source
226
-                });
227
-                modify.up(); // end of ssrc-group
228
-            }
229
-        });
230
-
231
-        modify.up(); // end of description
232
-        modify.up(); // end of content
233
-    });
234
-
235
-    return modified;
236
-};
237
 
76
 
238
 // remove iSAC and CN from SDP
77
 // remove iSAC and CN from SDP
239
 SDP.prototype.mangle = function () {
78
 SDP.prototype.mangle = function () {
776
     return media;
615
     return media;
777
 };
616
 };
778
 
617
 
779
-SDPUtil = {
780
-    iceparams: function (mediadesc, sessiondesc) {
781
-        var data = null;
782
-        if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
783
-            SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
784
-            data = {
785
-                ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
786
-                pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
787
-            };
788
-        }
789
-        return data;
790
-    },
791
-    parse_iceufrag: function (line) {
792
-        return line.substring(12);
793
-    },
794
-    build_iceufrag: function (frag) {
795
-        return 'a=ice-ufrag:' + frag;
796
-    },
797
-    parse_icepwd: function (line) {
798
-        return line.substring(10);
799
-    },
800
-    build_icepwd: function (pwd) {
801
-        return 'a=ice-pwd:' + pwd;
802
-    },
803
-    parse_mid: function (line) {
804
-        return line.substring(6);
805
-    },
806
-    parse_mline: function (line) {
807
-        var parts = line.substring(2).split(' '),
808
-            data = {};
809
-        data.media = parts.shift();
810
-        data.port = parts.shift();
811
-        data.proto = parts.shift();
812
-        if (parts[parts.length - 1] === '') { // trailing whitespace
813
-            parts.pop();
814
-        }
815
-        data.fmt = parts;
816
-        return data;
817
-    },
818
-    build_mline: function (mline) {
819
-        return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
820
-    },
821
-    parse_rtpmap: function (line) {
822
-        var parts = line.substring(9).split(' '),
823
-            data = {};
824
-        data.id = parts.shift();
825
-        parts = parts[0].split('/');
826
-        data.name = parts.shift();
827
-        data.clockrate = parts.shift();
828
-        data.channels = parts.length ? parts.shift() : '1';
829
-        return data;
830
-    },
831
-    /**
832
-     * Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
833
-     * @param line eg. "a=sctpmap:5000 webrtc-datachannel"
834
-     * @returns [SCTP port number, protocol, streams]
835
-     */
836
-    parse_sctpmap: function (line)
837
-    {
838
-        var parts = line.substring(10).split(' ');
839
-        var sctpPort = parts[0];
840
-        var protocol = parts[1];
841
-        // Stream count is optional
842
-        var streamCount = parts.length > 2 ? parts[2] : null;
843
-        return [sctpPort, protocol, streamCount];// SCTP port
844
-    },
845
-    build_rtpmap: function (el) {
846
-        var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
847
-        if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
848
-            line += '/' + el.getAttribute('channels');
849
-        }
850
-        return line;
851
-    },
852
-    parse_crypto: function (line) {
853
-        var parts = line.substring(9).split(' '),
854
-            data = {};
855
-        data.tag = parts.shift();
856
-        data['crypto-suite'] = parts.shift();
857
-        data['key-params'] = parts.shift();
858
-        if (parts.length) {
859
-            data['session-params'] = parts.join(' ');
860
-        }
861
-        return data;
862
-    },
863
-    parse_fingerprint: function (line) { // RFC 4572
864
-        var parts = line.substring(14).split(' '),
865
-            data = {};
866
-        data.hash = parts.shift();
867
-        data.fingerprint = parts.shift();
868
-        // TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
869
-        return data;
870
-    },
871
-    parse_fmtp: function (line) {
872
-        var parts = line.split(' '),
873
-            i, key, value,
874
-            data = [];
875
-        parts.shift();
876
-        parts = parts.join(' ').split(';');
877
-        for (i = 0; i < parts.length; i++) {
878
-            key = parts[i].split('=')[0];
879
-            while (key.length && key[0] == ' ') {
880
-                key = key.substring(1);
881
-            }
882
-            value = parts[i].split('=')[1];
883
-            if (key && value) {
884
-                data.push({name: key, value: value});
885
-            } else if (key) {
886
-                // rfc 4733 (DTMF) style stuff
887
-                data.push({name: '', value: key});
888
-            }
889
-        }
890
-        return data;
891
-    },
892
-    parse_icecandidate: function (line) {
893
-        var candidate = {},
894
-            elems = line.split(' ');
895
-        candidate.foundation = elems[0].substring(12);
896
-        candidate.component = elems[1];
897
-        candidate.protocol = elems[2].toLowerCase();
898
-        candidate.priority = elems[3];
899
-        candidate.ip = elems[4];
900
-        candidate.port = elems[5];
901
-        // elems[6] => "typ"
902
-        candidate.type = elems[7];
903
-        candidate.generation = 0; // default value, may be overwritten below
904
-        for (var i = 8; i < elems.length; i += 2) {
905
-            switch (elems[i]) {
906
-                case 'raddr':
907
-                    candidate['rel-addr'] = elems[i + 1];
908
-                    break;
909
-                case 'rport':
910
-                    candidate['rel-port'] = elems[i + 1];
911
-                    break;
912
-                case 'generation':
913
-                    candidate.generation = elems[i + 1];
914
-                    break;
915
-                case 'tcptype':
916
-                    candidate.tcptype = elems[i + 1];
917
-                    break;
918
-                default: // TODO
919
-                    console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
920
-            }
921
-        }
922
-        candidate.network = '1';
923
-        candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
924
-        return candidate;
925
-    },
926
-    build_icecandidate: function (cand) {
927
-        var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
928
-        line += ' ';
929
-        switch (cand.type) {
930
-            case 'srflx':
931
-            case 'prflx':
932
-            case 'relay':
933
-                if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
934
-                    line += 'raddr';
935
-                    line += ' ';
936
-                    line += cand['rel-addr'];
937
-                    line += ' ';
938
-                    line += 'rport';
939
-                    line += ' ';
940
-                    line += cand['rel-port'];
941
-                    line += ' ';
942
-                }
943
-                break;
944
-        }
945
-        if (cand.hasOwnAttribute('tcptype')) {
946
-            line += 'tcptype';
947
-            line += ' ';
948
-            line += cand.tcptype;
949
-            line += ' ';
950
-        }
951
-        line += 'generation';
952
-        line += ' ';
953
-        line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
954
-        return line;
955
-    },
956
-    parse_ssrc: function (desc) {
957
-        // proprietary mapping of a=ssrc lines
958
-        // TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
959
-        // and parse according to that
960
-        var lines = desc.split('\r\n'),
961
-            data = {};
962
-        for (var i = 0; i < lines.length; i++) {
963
-            if (lines[i].substring(0, 7) == 'a=ssrc:') {
964
-                var idx = lines[i].indexOf(' ');
965
-                data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
966
-            }
967
-        }
968
-        return data;
969
-    },
970
-    parse_rtcpfb: function (line) {
971
-        var parts = line.substr(10).split(' ');
972
-        var data = {};
973
-        data.pt = parts.shift();
974
-        data.type = parts.shift();
975
-        data.params = parts;
976
-        return data;
977
-    },
978
-    parse_extmap: function (line) {
979
-        var parts = line.substr(9).split(' ');
980
-        var data = {};
981
-        data.value = parts.shift();
982
-        if (data.value.indexOf('/') != -1) {
983
-            data.direction = data.value.substr(data.value.indexOf('/') + 1);
984
-            data.value = data.value.substr(0, data.value.indexOf('/'));
985
-        } else {
986
-            data.direction = 'both';
987
-        }
988
-        data.uri = parts.shift();
989
-        data.params = parts;
990
-        return data;
991
-    },
992
-    find_line: function (haystack, needle, sessionpart) {
993
-        var lines = haystack.split('\r\n');
994
-        for (var i = 0; i < lines.length; i++) {
995
-            if (lines[i].substring(0, needle.length) == needle) {
996
-                return lines[i];
997
-            }
998
-        }
999
-        if (!sessionpart) {
1000
-            return false;
1001
-        }
1002
-        // search session part
1003
-        lines = sessionpart.split('\r\n');
1004
-        for (var j = 0; j < lines.length; j++) {
1005
-            if (lines[j].substring(0, needle.length) == needle) {
1006
-                return lines[j];
1007
-            }
1008
-        }
1009
-        return false;
1010
-    },
1011
-    find_lines: function (haystack, needle, sessionpart) {
1012
-        var lines = haystack.split('\r\n'),
1013
-            needles = [];
1014
-        for (var i = 0; i < lines.length; i++) {
1015
-            if (lines[i].substring(0, needle.length) == needle)
1016
-                needles.push(lines[i]);
1017
-        }
1018
-        if (needles.length || !sessionpart) {
1019
-            return needles;
1020
-        }
1021
-        // search session part
1022
-        lines = sessionpart.split('\r\n');
1023
-        for (var j = 0; j < lines.length; j++) {
1024
-            if (lines[j].substring(0, needle.length) == needle) {
1025
-                needles.push(lines[j]);
1026
-            }
1027
-        }
1028
-        return needles;
1029
-    },
1030
-    candidateToJingle: function (line) {
1031
-        // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
1032
-        //      <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
1033
-        if (line.indexOf('candidate:') === 0) {
1034
-            line = 'a=' + line;
1035
-        } else if (line.substring(0, 12) != 'a=candidate:') {
1036
-            console.log('parseCandidate called with a line that is not a candidate line');
1037
-            console.log(line);
1038
-            return null;
1039
-        }
1040
-        if (line.substring(line.length - 2) == '\r\n') // chomp it
1041
-            line = line.substring(0, line.length - 2);
1042
-        var candidate = {},
1043
-            elems = line.split(' '),
1044
-            i;
1045
-        if (elems[6] != 'typ') {
1046
-            console.log('did not find typ in the right place');
1047
-            console.log(line);
1048
-            return null;
1049
-        }
1050
-        candidate.foundation = elems[0].substring(12);
1051
-        candidate.component = elems[1];
1052
-        candidate.protocol = elems[2].toLowerCase();
1053
-        candidate.priority = elems[3];
1054
-        candidate.ip = elems[4];
1055
-        candidate.port = elems[5];
1056
-        // elems[6] => "typ"
1057
-        candidate.type = elems[7];
1058
 
618
 
1059
-        candidate.generation = '0'; // default, may be overwritten below
1060
-        for (i = 8; i < elems.length; i += 2) {
1061
-            switch (elems[i]) {
1062
-                case 'raddr':
1063
-                    candidate['rel-addr'] = elems[i + 1];
1064
-                    break;
1065
-                case 'rport':
1066
-                    candidate['rel-port'] = elems[i + 1];
1067
-                    break;
1068
-                case 'generation':
1069
-                    candidate.generation = elems[i + 1];
1070
-                    break;
1071
-                case 'tcptype':
1072
-                    candidate.tcptype = elems[i + 1];
1073
-                    break;
1074
-                default: // TODO
1075
-                    console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
1076
-            }
1077
-        }
1078
-        candidate.network = '1';
1079
-        candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
1080
-        return candidate;
1081
-    },
1082
-    candidateFromJingle: function (cand) {
1083
-        var line = 'a=candidate:';
1084
-        line += cand.getAttribute('foundation');
1085
-        line += ' ';
1086
-        line += cand.getAttribute('component');
1087
-        line += ' ';
1088
-        line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
1089
-        line += ' ';
1090
-        line += cand.getAttribute('priority');
1091
-        line += ' ';
1092
-        line += cand.getAttribute('ip');
1093
-        line += ' ';
1094
-        line += cand.getAttribute('port');
1095
-        line += ' ';
1096
-        line += 'typ';
1097
-        line += ' ' + cand.getAttribute('type');
1098
-        line += ' ';
1099
-        switch (cand.getAttribute('type')) {
1100
-            case 'srflx':
1101
-            case 'prflx':
1102
-            case 'relay':
1103
-                if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
1104
-                    line += 'raddr';
1105
-                    line += ' ';
1106
-                    line += cand.getAttribute('rel-addr');
1107
-                    line += ' ';
1108
-                    line += 'rport';
1109
-                    line += ' ';
1110
-                    line += cand.getAttribute('rel-port');
1111
-                    line += ' ';
1112
-                }
1113
-                break;
1114
-        }
1115
-        if (cand.getAttribute('protocol').toLowerCase() == 'tcp') {
1116
-            line += 'tcptype';
1117
-            line += ' ';
1118
-            line += cand.getAttribute('tcptype');
1119
-            line += ' ';
1120
-        }
1121
-        line += 'generation';
1122
-        line += ' ';
1123
-        line += cand.getAttribute('generation') || '0';
1124
-        return line + '\r\n';
1125
-    }
1126
-};
619
+module.exports = SDP;
1127
 
620
 

+ 165
- 0
modules/xmpp/SDPDiffer.js View File

1
+function SDPDiffer(mySDP, otherSDP) {
2
+    this.mySDP = mySDP;
3
+    this.otherSDP = otherSDP;
4
+}
5
+
6
+/**
7
+ * Returns map of MediaChannel that contains only media not contained in <tt>otherSdp</tt>. Mapped by channel idx.
8
+ * @param otherSdp the other SDP to check ssrc with.
9
+ */
10
+SDPDiffer.prototype.getNewMedia = function() {
11
+
12
+    // this could be useful in Array.prototype.
13
+    function arrayEquals(array) {
14
+        // if the other array is a falsy value, return
15
+        if (!array)
16
+            return false;
17
+
18
+        // compare lengths - can save a lot of time
19
+        if (this.length != array.length)
20
+            return false;
21
+
22
+        for (var i = 0, l=this.length; i < l; i++) {
23
+            // Check if we have nested arrays
24
+            if (this[i] instanceof Array && array[i] instanceof Array) {
25
+                // recurse into the nested arrays
26
+                if (!this[i].equals(array[i]))
27
+                    return false;
28
+            }
29
+            else if (this[i] != array[i]) {
30
+                // Warning - two different object instances will never be equal: {x:20} != {x:20}
31
+                return false;
32
+            }
33
+        }
34
+        return true;
35
+    }
36
+
37
+    var myMedias = this.mySDP.getMediaSsrcMap();
38
+    var othersMedias = this.otherSDP.getMediaSsrcMap();
39
+    var newMedia = {};
40
+    Object.keys(othersMedias).forEach(function(othersMediaIdx) {
41
+        var myMedia = myMedias[othersMediaIdx];
42
+        var othersMedia = othersMedias[othersMediaIdx];
43
+        if(!myMedia && othersMedia) {
44
+            // Add whole channel
45
+            newMedia[othersMediaIdx] = othersMedia;
46
+            return;
47
+        }
48
+        // Look for new ssrcs accross the channel
49
+        Object.keys(othersMedia.ssrcs).forEach(function(ssrc) {
50
+            if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) {
51
+                // Allocate channel if we've found ssrc that doesn't exist in our channel
52
+                if(!newMedia[othersMediaIdx]){
53
+                    newMedia[othersMediaIdx] = {
54
+                        mediaindex: othersMedia.mediaindex,
55
+                        mid: othersMedia.mid,
56
+                        ssrcs: {},
57
+                        ssrcGroups: []
58
+                    };
59
+                }
60
+                newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc];
61
+            }
62
+        });
63
+
64
+        // Look for new ssrc groups across the channels
65
+        othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){
66
+
67
+            // try to match the other ssrc-group with an ssrc-group of ours
68
+            var matched = false;
69
+            for (var i = 0; i < myMedia.ssrcGroups.length; i++) {
70
+                var mySsrcGroup = myMedia.ssrcGroups[i];
71
+                if (otherSsrcGroup.semantics == mySsrcGroup.semantics
72
+                    && arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) {
73
+
74
+                    matched = true;
75
+                    break;
76
+                }
77
+            }
78
+
79
+            if (!matched) {
80
+                // Allocate channel if we've found an ssrc-group that doesn't
81
+                // exist in our channel
82
+
83
+                if(!newMedia[othersMediaIdx]){
84
+                    newMedia[othersMediaIdx] = {
85
+                        mediaindex: othersMedia.mediaindex,
86
+                        mid: othersMedia.mid,
87
+                        ssrcs: {},
88
+                        ssrcGroups: []
89
+                    };
90
+                }
91
+                newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup);
92
+            }
93
+        });
94
+    });
95
+    return newMedia;
96
+};
97
+
98
+/**
99
+ * Sends SSRC update IQ.
100
+ * @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
101
+ * @param sid session identifier that will be put into the IQ.
102
+ * @param initiator initiator identifier.
103
+ * @param toJid destination Jid
104
+ * @param isAdd indicates if this is remove or add operation.
105
+ */
106
+SDPDiffer.prototype.toJingle = function(modify) {
107
+    var sdpMediaSsrcs = this.getNewMedia();
108
+    var self = this;
109
+
110
+    // FIXME: only announce video ssrcs since we mix audio and dont need
111
+    //      the audio ssrcs therefore
112
+    var modified = false;
113
+    Object.keys(sdpMediaSsrcs).forEach(function(mediaindex){
114
+        modified = true;
115
+        var media = sdpMediaSsrcs[mediaindex];
116
+        modify.c('content', {name: media.mid});
117
+
118
+        modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: media.mid});
119
+        // FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
120
+        // generate sources from lines
121
+        Object.keys(media.ssrcs).forEach(function(ssrcNum) {
122
+            var mediaSsrc = media.ssrcs[ssrcNum];
123
+            modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
124
+            modify.attrs({ssrc: mediaSsrc.ssrc});
125
+            // iterate over ssrc lines
126
+            mediaSsrc.lines.forEach(function (line) {
127
+                var idx = line.indexOf(' ');
128
+                var kv = line.substr(idx + 1);
129
+                modify.c('parameter');
130
+                if (kv.indexOf(':') == -1) {
131
+                    modify.attrs({ name: kv });
132
+                } else {
133
+                    modify.attrs({ name: kv.split(':', 2)[0] });
134
+                    modify.attrs({ value: kv.split(':', 2)[1] });
135
+                }
136
+                modify.up(); // end of parameter
137
+            });
138
+            modify.up(); // end of source
139
+        });
140
+
141
+        // generate source groups from lines
142
+        media.ssrcGroups.forEach(function(ssrcGroup) {
143
+            if (ssrcGroup.ssrcs.length != 0) {
144
+
145
+                modify.c('ssrc-group', {
146
+                    semantics: ssrcGroup.semantics,
147
+                    xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
148
+                });
149
+
150
+                ssrcGroup.ssrcs.forEach(function (ssrc) {
151
+                    modify.c('source', { ssrc: ssrc })
152
+                        .up(); // end of source
153
+                });
154
+                modify.up(); // end of ssrc-group
155
+            }
156
+        });
157
+
158
+        modify.up(); // end of description
159
+        modify.up(); // end of content
160
+    });
161
+
162
+    return modified;
163
+};
164
+
165
+module.exports = SDPDiffer;

+ 349
- 0
modules/xmpp/SDPUtil.js View File

1
+SDPUtil = {
2
+    iceparams: function (mediadesc, sessiondesc) {
3
+        var data = null;
4
+        if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
5
+            SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
6
+            data = {
7
+                ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
8
+                pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
9
+            };
10
+        }
11
+        return data;
12
+    },
13
+    parse_iceufrag: function (line) {
14
+        return line.substring(12);
15
+    },
16
+    build_iceufrag: function (frag) {
17
+        return 'a=ice-ufrag:' + frag;
18
+    },
19
+    parse_icepwd: function (line) {
20
+        return line.substring(10);
21
+    },
22
+    build_icepwd: function (pwd) {
23
+        return 'a=ice-pwd:' + pwd;
24
+    },
25
+    parse_mid: function (line) {
26
+        return line.substring(6);
27
+    },
28
+    parse_mline: function (line) {
29
+        var parts = line.substring(2).split(' '),
30
+            data = {};
31
+        data.media = parts.shift();
32
+        data.port = parts.shift();
33
+        data.proto = parts.shift();
34
+        if (parts[parts.length - 1] === '') { // trailing whitespace
35
+            parts.pop();
36
+        }
37
+        data.fmt = parts;
38
+        return data;
39
+    },
40
+    build_mline: function (mline) {
41
+        return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
42
+    },
43
+    parse_rtpmap: function (line) {
44
+        var parts = line.substring(9).split(' '),
45
+            data = {};
46
+        data.id = parts.shift();
47
+        parts = parts[0].split('/');
48
+        data.name = parts.shift();
49
+        data.clockrate = parts.shift();
50
+        data.channels = parts.length ? parts.shift() : '1';
51
+        return data;
52
+    },
53
+    /**
54
+     * Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
55
+     * @param line eg. "a=sctpmap:5000 webrtc-datachannel"
56
+     * @returns [SCTP port number, protocol, streams]
57
+     */
58
+    parse_sctpmap: function (line)
59
+    {
60
+        var parts = line.substring(10).split(' ');
61
+        var sctpPort = parts[0];
62
+        var protocol = parts[1];
63
+        // Stream count is optional
64
+        var streamCount = parts.length > 2 ? parts[2] : null;
65
+        return [sctpPort, protocol, streamCount];// SCTP port
66
+    },
67
+    build_rtpmap: function (el) {
68
+        var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
69
+        if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
70
+            line += '/' + el.getAttribute('channels');
71
+        }
72
+        return line;
73
+    },
74
+    parse_crypto: function (line) {
75
+        var parts = line.substring(9).split(' '),
76
+            data = {};
77
+        data.tag = parts.shift();
78
+        data['crypto-suite'] = parts.shift();
79
+        data['key-params'] = parts.shift();
80
+        if (parts.length) {
81
+            data['session-params'] = parts.join(' ');
82
+        }
83
+        return data;
84
+    },
85
+    parse_fingerprint: function (line) { // RFC 4572
86
+        var parts = line.substring(14).split(' '),
87
+            data = {};
88
+        data.hash = parts.shift();
89
+        data.fingerprint = parts.shift();
90
+        // TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
91
+        return data;
92
+    },
93
+    parse_fmtp: function (line) {
94
+        var parts = line.split(' '),
95
+            i, key, value,
96
+            data = [];
97
+        parts.shift();
98
+        parts = parts.join(' ').split(';');
99
+        for (i = 0; i < parts.length; i++) {
100
+            key = parts[i].split('=')[0];
101
+            while (key.length && key[0] == ' ') {
102
+                key = key.substring(1);
103
+            }
104
+            value = parts[i].split('=')[1];
105
+            if (key && value) {
106
+                data.push({name: key, value: value});
107
+            } else if (key) {
108
+                // rfc 4733 (DTMF) style stuff
109
+                data.push({name: '', value: key});
110
+            }
111
+        }
112
+        return data;
113
+    },
114
+    parse_icecandidate: function (line) {
115
+        var candidate = {},
116
+            elems = line.split(' ');
117
+        candidate.foundation = elems[0].substring(12);
118
+        candidate.component = elems[1];
119
+        candidate.protocol = elems[2].toLowerCase();
120
+        candidate.priority = elems[3];
121
+        candidate.ip = elems[4];
122
+        candidate.port = elems[5];
123
+        // elems[6] => "typ"
124
+        candidate.type = elems[7];
125
+        candidate.generation = 0; // default value, may be overwritten below
126
+        for (var i = 8; i < elems.length; i += 2) {
127
+            switch (elems[i]) {
128
+                case 'raddr':
129
+                    candidate['rel-addr'] = elems[i + 1];
130
+                    break;
131
+                case 'rport':
132
+                    candidate['rel-port'] = elems[i + 1];
133
+                    break;
134
+                case 'generation':
135
+                    candidate.generation = elems[i + 1];
136
+                    break;
137
+                case 'tcptype':
138
+                    candidate.tcptype = elems[i + 1];
139
+                    break;
140
+                default: // TODO
141
+                    console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
142
+            }
143
+        }
144
+        candidate.network = '1';
145
+        candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
146
+        return candidate;
147
+    },
148
+    build_icecandidate: function (cand) {
149
+        var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
150
+        line += ' ';
151
+        switch (cand.type) {
152
+            case 'srflx':
153
+            case 'prflx':
154
+            case 'relay':
155
+                if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
156
+                    line += 'raddr';
157
+                    line += ' ';
158
+                    line += cand['rel-addr'];
159
+                    line += ' ';
160
+                    line += 'rport';
161
+                    line += ' ';
162
+                    line += cand['rel-port'];
163
+                    line += ' ';
164
+                }
165
+                break;
166
+        }
167
+        if (cand.hasOwnAttribute('tcptype')) {
168
+            line += 'tcptype';
169
+            line += ' ';
170
+            line += cand.tcptype;
171
+            line += ' ';
172
+        }
173
+        line += 'generation';
174
+        line += ' ';
175
+        line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
176
+        return line;
177
+    },
178
+    parse_ssrc: function (desc) {
179
+        // proprietary mapping of a=ssrc lines
180
+        // TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
181
+        // and parse according to that
182
+        var lines = desc.split('\r\n'),
183
+            data = {};
184
+        for (var i = 0; i < lines.length; i++) {
185
+            if (lines[i].substring(0, 7) == 'a=ssrc:') {
186
+                var idx = lines[i].indexOf(' ');
187
+                data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
188
+            }
189
+        }
190
+        return data;
191
+    },
192
+    parse_rtcpfb: function (line) {
193
+        var parts = line.substr(10).split(' ');
194
+        var data = {};
195
+        data.pt = parts.shift();
196
+        data.type = parts.shift();
197
+        data.params = parts;
198
+        return data;
199
+    },
200
+    parse_extmap: function (line) {
201
+        var parts = line.substr(9).split(' ');
202
+        var data = {};
203
+        data.value = parts.shift();
204
+        if (data.value.indexOf('/') != -1) {
205
+            data.direction = data.value.substr(data.value.indexOf('/') + 1);
206
+            data.value = data.value.substr(0, data.value.indexOf('/'));
207
+        } else {
208
+            data.direction = 'both';
209
+        }
210
+        data.uri = parts.shift();
211
+        data.params = parts;
212
+        return data;
213
+    },
214
+    find_line: function (haystack, needle, sessionpart) {
215
+        var lines = haystack.split('\r\n');
216
+        for (var i = 0; i < lines.length; i++) {
217
+            if (lines[i].substring(0, needle.length) == needle) {
218
+                return lines[i];
219
+            }
220
+        }
221
+        if (!sessionpart) {
222
+            return false;
223
+        }
224
+        // search session part
225
+        lines = sessionpart.split('\r\n');
226
+        for (var j = 0; j < lines.length; j++) {
227
+            if (lines[j].substring(0, needle.length) == needle) {
228
+                return lines[j];
229
+            }
230
+        }
231
+        return false;
232
+    },
233
+    find_lines: function (haystack, needle, sessionpart) {
234
+        var lines = haystack.split('\r\n'),
235
+            needles = [];
236
+        for (var i = 0; i < lines.length; i++) {
237
+            if (lines[i].substring(0, needle.length) == needle)
238
+                needles.push(lines[i]);
239
+        }
240
+        if (needles.length || !sessionpart) {
241
+            return needles;
242
+        }
243
+        // search session part
244
+        lines = sessionpart.split('\r\n');
245
+        for (var j = 0; j < lines.length; j++) {
246
+            if (lines[j].substring(0, needle.length) == needle) {
247
+                needles.push(lines[j]);
248
+            }
249
+        }
250
+        return needles;
251
+    },
252
+    candidateToJingle: function (line) {
253
+        // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
254
+        //      <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
255
+        if (line.indexOf('candidate:') === 0) {
256
+            line = 'a=' + line;
257
+        } else if (line.substring(0, 12) != 'a=candidate:') {
258
+            console.log('parseCandidate called with a line that is not a candidate line');
259
+            console.log(line);
260
+            return null;
261
+        }
262
+        if (line.substring(line.length - 2) == '\r\n') // chomp it
263
+            line = line.substring(0, line.length - 2);
264
+        var candidate = {},
265
+            elems = line.split(' '),
266
+            i;
267
+        if (elems[6] != 'typ') {
268
+            console.log('did not find typ in the right place');
269
+            console.log(line);
270
+            return null;
271
+        }
272
+        candidate.foundation = elems[0].substring(12);
273
+        candidate.component = elems[1];
274
+        candidate.protocol = elems[2].toLowerCase();
275
+        candidate.priority = elems[3];
276
+        candidate.ip = elems[4];
277
+        candidate.port = elems[5];
278
+        // elems[6] => "typ"
279
+        candidate.type = elems[7];
280
+
281
+        candidate.generation = '0'; // default, may be overwritten below
282
+        for (i = 8; i < elems.length; i += 2) {
283
+            switch (elems[i]) {
284
+                case 'raddr':
285
+                    candidate['rel-addr'] = elems[i + 1];
286
+                    break;
287
+                case 'rport':
288
+                    candidate['rel-port'] = elems[i + 1];
289
+                    break;
290
+                case 'generation':
291
+                    candidate.generation = elems[i + 1];
292
+                    break;
293
+                case 'tcptype':
294
+                    candidate.tcptype = elems[i + 1];
295
+                    break;
296
+                default: // TODO
297
+                    console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
298
+            }
299
+        }
300
+        candidate.network = '1';
301
+        candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
302
+        return candidate;
303
+    },
304
+    candidateFromJingle: function (cand) {
305
+        var line = 'a=candidate:';
306
+        line += cand.getAttribute('foundation');
307
+        line += ' ';
308
+        line += cand.getAttribute('component');
309
+        line += ' ';
310
+        line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
311
+        line += ' ';
312
+        line += cand.getAttribute('priority');
313
+        line += ' ';
314
+        line += cand.getAttribute('ip');
315
+        line += ' ';
316
+        line += cand.getAttribute('port');
317
+        line += ' ';
318
+        line += 'typ';
319
+        line += ' ' + cand.getAttribute('type');
320
+        line += ' ';
321
+        switch (cand.getAttribute('type')) {
322
+            case 'srflx':
323
+            case 'prflx':
324
+            case 'relay':
325
+                if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
326
+                    line += 'raddr';
327
+                    line += ' ';
328
+                    line += cand.getAttribute('rel-addr');
329
+                    line += ' ';
330
+                    line += 'rport';
331
+                    line += ' ';
332
+                    line += cand.getAttribute('rel-port');
333
+                    line += ' ';
334
+                }
335
+                break;
336
+        }
337
+        if (cand.getAttribute('protocol').toLowerCase() == 'tcp') {
338
+            line += 'tcptype';
339
+            line += ' ';
340
+            line += cand.getAttribute('tcptype');
341
+            line += ' ';
342
+        }
343
+        line += 'generation';
344
+        line += ' ';
345
+        line += cand.getAttribute('generation') || '0';
346
+        return line + '\r\n';
347
+    }
348
+};
349
+module.exports = SDPUtil;

libs/strophe/strophe.jingle.adapter.js → modules/xmpp/TraceablePeerConnection.js View File

262
     }
262
     }
263
 };
263
 };
264
 
264
 
265
+module.exports = TraceablePeerConnection;
266
+

moderator.js → modules/xmpp/moderator.js View File

1
-/* global $, $iq, config, connection, Etherpad, hangUp, messageHandler,
1
+/* global $, $iq, config, connection, UI, messageHandler,
2
  roomName, sessionTerminated, Strophe, Util */
2
  roomName, sessionTerminated, Strophe, Util */
3
 /**
3
 /**
4
  * Contains logic responsible for enabling/disabling functionality available
4
  * Contains logic responsible for enabling/disabling functionality available
5
  * only to moderator users.
5
  * only to moderator users.
6
  */
6
  */
7
-var Moderator = (function (my) {
8
-
9
-    var focusUserJid;
10
-    var getNextTimeout = Util.createExpBackoffTimer(1000);
11
-    var getNextErrorTimeout = Util.createExpBackoffTimer(1000);
12
-    // External authentication stuff
13
-    var externalAuthEnabled = false;
14
-    // Sip gateway can be enabled by configuring Jigasi host in config.js or
15
-    // it will be enabled automatically if focus detects the component through
16
-    // service discovery.
17
-    var sipGatewayEnabled = config.hosts.call_control !== undefined;
18
-
19
-    my.isModerator = function () {
7
+var connection = null;
8
+var focusUserJid;
9
+var getNextTimeout = Util.createExpBackoffTimer(1000);
10
+var getNextErrorTimeout = Util.createExpBackoffTimer(1000);
11
+// External authentication stuff
12
+var externalAuthEnabled = false;
13
+// Sip gateway can be enabled by configuring Jigasi host in config.js or
14
+// it will be enabled automatically if focus detects the component through
15
+// service discovery.
16
+var sipGatewayEnabled = config.hosts.call_control !== undefined;
17
+
18
+var Moderator = {
19
+    isModerator: function () {
20
         return connection && connection.emuc.isModerator();
20
         return connection && connection.emuc.isModerator();
21
-    };
21
+    },
22
 
22
 
23
-    my.isPeerModerator = function (peerJid) {
24
-        return connection && connection.emuc.getMemberRole(peerJid) === 'moderator';
25
-    };
23
+    isPeerModerator: function (peerJid) {
24
+        return connection &&
25
+            connection.emuc.getMemberRole(peerJid) === 'moderator';
26
+    },
26
 
27
 
27
-    my.isExternalAuthEnabled = function () {
28
+    isExternalAuthEnabled: function () {
28
         return externalAuthEnabled;
29
         return externalAuthEnabled;
29
-    };
30
+    },
30
 
31
 
31
-    my.isSipGatewayEnabled = function () {
32
+    isSipGatewayEnabled: function () {
32
         return sipGatewayEnabled;
33
         return sipGatewayEnabled;
33
-    };
34
+    },
34
 
35
 
35
-    my.init = function () {
36
-        Moderator.onLocalRoleChange = function (from, member, pres) {
36
+    setConnection: function (con) {
37
+        connection = con;
38
+    },
39
+
40
+    init: function (xmpp) {
41
+        this.xmppService = xmpp;
42
+        this.onLocalRoleChange = function (from, member, pres) {
37
             UI.onModeratorStatusChanged(Moderator.isModerator());
43
             UI.onModeratorStatusChanged(Moderator.isModerator());
38
         };
44
         };
39
-    };
45
+    },
40
 
46
 
41
-    my.onMucLeft = function (jid) {
47
+    onMucLeft: function (jid) {
42
         console.info("Someone left is it focus ? " + jid);
48
         console.info("Someone left is it focus ? " + jid);
43
         var resource = Strophe.getResourceFromJid(jid);
49
         var resource = Strophe.getResourceFromJid(jid);
44
-        if (resource === 'focus' && !sessionTerminated) {
50
+        if (resource === 'focus' && !this.xmppService.sessionTerminated) {
45
             console.info(
51
             console.info(
46
                 "Focus has left the room - leaving conference");
52
                 "Focus has left the room - leaving conference");
47
             //hangUp();
53
             //hangUp();
49
             // FIXME: show some message before reload
55
             // FIXME: show some message before reload
50
             location.reload();
56
             location.reload();
51
         }
57
         }
52
-    }
53
-
54
-    my.setFocusUserJid = function (focusJid) {
58
+    },
59
+    
60
+    setFocusUserJid: function (focusJid) {
55
         if (!focusUserJid) {
61
         if (!focusUserJid) {
56
             focusUserJid = focusJid;
62
             focusUserJid = focusJid;
57
             console.info("Focus jid set to: " + focusUserJid);
63
             console.info("Focus jid set to: " + focusUserJid);
58
         }
64
         }
59
-    };
65
+    },
60
 
66
 
61
-    my.getFocusUserJid = function () {
67
+    getFocusUserJid: function () {
62
         return focusUserJid;
68
         return focusUserJid;
63
-    };
69
+    },
64
 
70
 
65
-    my.getFocusComponent = function () {
71
+    getFocusComponent: function () {
66
         // Get focus component address
72
         // Get focus component address
67
         var focusComponent = config.hosts.focus;
73
         var focusComponent = config.hosts.focus;
68
         // If not specified use default: 'focus.domain'
74
         // If not specified use default: 'focus.domain'
70
             focusComponent = 'focus.' + config.hosts.domain;
76
             focusComponent = 'focus.' + config.hosts.domain;
71
         }
77
         }
72
         return focusComponent;
78
         return focusComponent;
73
-    };
79
+    },
74
 
80
 
75
-    my.createConferenceIq = function () {
81
+    createConferenceIq: function (roomName) {
76
         // Generate create conference IQ
82
         // Generate create conference IQ
77
         var elem = $iq({to: Moderator.getFocusComponent(), type: 'set'});
83
         var elem = $iq({to: Moderator.getFocusComponent(), type: 'set'});
78
         elem.c('conference', {
84
         elem.c('conference', {
79
             xmlns: 'http://jitsi.org/protocol/focus',
85
             xmlns: 'http://jitsi.org/protocol/focus',
80
             room: roomName
86
             room: roomName
81
         });
87
         });
82
-        if (config.hosts.bridge !== undefined)
83
-        {
88
+        if (config.hosts.bridge !== undefined) {
84
             elem.c(
89
             elem.c(
85
                 'property',
90
                 'property',
86
                 { name: 'bridge', value: config.hosts.bridge})
91
                 { name: 'bridge', value: config.hosts.bridge})
87
                 .up();
92
                 .up();
88
         }
93
         }
89
         // Tell the focus we have Jigasi configured
94
         // Tell the focus we have Jigasi configured
90
-        if (config.hosts.call_control !== undefined)
91
-        {
95
+        if (config.hosts.call_control !== undefined) {
92
             elem.c(
96
             elem.c(
93
                 'property',
97
                 'property',
94
                 { name: 'call_control', value: config.hosts.call_control})
98
                 { name: 'call_control', value: config.hosts.call_control})
95
                 .up();
99
                 .up();
96
         }
100
         }
97
-        if (config.channelLastN !== undefined)
98
-        {
101
+        if (config.channelLastN !== undefined) {
99
             elem.c(
102
             elem.c(
100
                 'property',
103
                 'property',
101
                 { name: 'channelLastN', value: config.channelLastN})
104
                 { name: 'channelLastN', value: config.channelLastN})
102
                 .up();
105
                 .up();
103
         }
106
         }
104
-        if (config.adaptiveLastN !== undefined)
105
-        {
107
+        if (config.adaptiveLastN !== undefined) {
106
             elem.c(
108
             elem.c(
107
                 'property',
109
                 'property',
108
                 { name: 'adaptiveLastN', value: config.adaptiveLastN})
110
                 { name: 'adaptiveLastN', value: config.adaptiveLastN})
109
                 .up();
111
                 .up();
110
         }
112
         }
111
-        if (config.adaptiveSimulcast !== undefined)
112
-        {
113
+        if (config.adaptiveSimulcast !== undefined) {
113
             elem.c(
114
             elem.c(
114
                 'property',
115
                 'property',
115
                 { name: 'adaptiveSimulcast', value: config.adaptiveSimulcast})
116
                 { name: 'adaptiveSimulcast', value: config.adaptiveSimulcast})
116
                 .up();
117
                 .up();
117
         }
118
         }
118
-        if (config.openSctp !== undefined)
119
-        {
119
+        if (config.openSctp !== undefined) {
120
             elem.c(
120
             elem.c(
121
                 'property',
121
                 'property',
122
                 { name: 'openSctp', value: config.openSctp})
122
                 { name: 'openSctp', value: config.openSctp})
123
                 .up();
123
                 .up();
124
         }
124
         }
125
-        if (config.enableFirefoxSupport !== undefined)
126
-        {
125
+        if (config.enableFirefoxSupport !== undefined) {
127
             elem.c(
126
             elem.c(
128
                 'property',
127
                 'property',
129
-                { name: 'enableFirefoxHacks', value: config.enableFirefoxSupport})
128
+                { name: 'enableFirefoxHacks',
129
+                    value: config.enableFirefoxSupport})
130
                 .up();
130
                 .up();
131
         }
131
         }
132
         elem.up();
132
         elem.up();
133
         return elem;
133
         return elem;
134
-    };
135
-
136
-    my.parseConfigOptions = function (resultIq) {
134
+    },
137
 
135
 
136
+    parseConfigOptions: function (resultIq) {
137
+    
138
         Moderator.setFocusUserJid(
138
         Moderator.setFocusUserJid(
139
             $(resultIq).find('conference').attr('focusjid'));
139
             $(resultIq).find('conference').attr('focusjid'));
140
-
140
+    
141
         var extAuthParam
141
         var extAuthParam
142
             = $(resultIq).find('>conference>property[name=\'externalAuth\']');
142
             = $(resultIq).find('>conference>property[name=\'externalAuth\']');
143
         if (extAuthParam.length) {
143
         if (extAuthParam.length) {
144
             externalAuthEnabled = extAuthParam.attr('value') === 'true';
144
             externalAuthEnabled = extAuthParam.attr('value') === 'true';
145
         }
145
         }
146
-
146
+    
147
         console.info("External authentication enabled: " + externalAuthEnabled);
147
         console.info("External authentication enabled: " + externalAuthEnabled);
148
-
148
+    
149
         // Check if focus has auto-detected Jigasi component(this will be also
149
         // Check if focus has auto-detected Jigasi component(this will be also
150
         // included if we have passed our host from the config)
150
         // included if we have passed our host from the config)
151
         if ($(resultIq).find(
151
         if ($(resultIq).find(
152
-                '>conference>property[name=\'sipGatewayEnabled\']').length) {
152
+            '>conference>property[name=\'sipGatewayEnabled\']').length) {
153
             sipGatewayEnabled = true;
153
             sipGatewayEnabled = true;
154
         }
154
         }
155
-
155
+    
156
         console.info("Sip gateway enabled: " + sipGatewayEnabled);
156
         console.info("Sip gateway enabled: " + sipGatewayEnabled);
157
-    };
157
+    },
158
 
158
 
159
     // FIXME: we need to show the fact that we're waiting for the focus
159
     // FIXME: we need to show the fact that we're waiting for the focus
160
     // to the user(or that focus is not available)
160
     // to the user(or that focus is not available)
161
-    my.allocateConferenceFocus = function (roomName, callback) {
161
+    allocateConferenceFocus: function (roomName, callback) {
162
         // Try to use focus user JID from the config
162
         // Try to use focus user JID from the config
163
         Moderator.setFocusUserJid(config.focusUserJid);
163
         Moderator.setFocusUserJid(config.focusUserJid);
164
         // Send create conference IQ
164
         // Send create conference IQ
165
-        var iq = Moderator.createConferenceIq();
165
+        var iq = Moderator.createConferenceIq(roomName);
166
         connection.sendIQ(
166
         connection.sendIQ(
167
             iq,
167
             iq,
168
             function (result) {
168
             function (result) {
190
                 // Not authorized to create new room
190
                 // Not authorized to create new room
191
                 if ($(error).find('>error>not-authorized').length) {
191
                 if ($(error).find('>error>not-authorized').length) {
192
                     console.warn("Unauthorized to start the conference");
192
                     console.warn("Unauthorized to start the conference");
193
-                    UI.onAuthenticationRequired();
193
+                    UI.onAuthenticationRequired(function () {
194
+                        Moderator.allocateConferenceFocus(roomName, callback);
195
+                    });
194
                     return;
196
                     return;
195
                 }
197
                 }
196
                 var waitMs = getNextErrorTimeout();
198
                 var waitMs = getNextErrorTimeout();
198
                 // Show message
200
                 // Show message
199
                 UI.messageHandler.notify(
201
                 UI.messageHandler.notify(
200
                     'Conference focus', 'disconnected',
202
                     'Conference focus', 'disconnected',
201
-                    Moderator.getFocusComponent() +
202
-                    ' not available - retry in ' + (waitMs / 1000) + ' sec');
203
+                        Moderator.getFocusComponent() +
204
+                        ' not available - retry in ' +
205
+                        (waitMs / 1000) + ' sec');
203
                 // Reset response timeout
206
                 // Reset response timeout
204
                 getNextTimeout(true);
207
                 getNextTimeout(true);
205
                 window.setTimeout(
208
                 window.setTimeout(
208
                     }, waitMs);
211
                     }, waitMs);
209
             }
212
             }
210
         );
213
         );
211
-    };
214
+    },
212
 
215
 
213
-    my.getAuthUrl = function (urlCallback) {
216
+    getAuthUrl: function (roomName, urlCallback) {
214
         var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
217
         var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
215
         iq.c('auth-url', {
218
         iq.c('auth-url', {
216
             xmlns: 'http://jitsi.org/protocol/focus',
219
             xmlns: 'http://jitsi.org/protocol/focus',
232
                 console.error("Get auth url error", error);
235
                 console.error("Get auth url error", error);
233
             }
236
             }
234
         );
237
         );
235
-    };
238
+    }
239
+};
236
 
240
 
237
-    return my;
238
-}(Moderator || {}));
241
+module.exports = Moderator;
239
 
242
 
240
 
243
 
241
 
244
 

+ 152
- 0
modules/xmpp/recording.js View File

1
+/* global $, $iq, config, connection, focusMucJid, messageHandler, Moderator,
2
+   Toolbar, Util */
3
+var Moderator = require("./moderator");
4
+
5
+
6
+var recordingToken = null;
7
+var recordingEnabled;
8
+
9
+/**
10
+ * Whether to use a jirecon component for recording, or use the videobridge
11
+ * through COLIBRI.
12
+ */
13
+var useJirecon = (typeof config.hosts.jirecon != "undefined");
14
+
15
+/**
16
+ * The ID of the jirecon recording session. Jirecon generates it when we
17
+ * initially start recording, and it needs to be used in subsequent requests
18
+ * to jirecon.
19
+ */
20
+var jireconRid = null;
21
+
22
+function setRecordingToken(token) {
23
+    recordingToken = token;
24
+}
25
+
26
+function setRecording(state, token, callback) {
27
+    if (useJirecon){
28
+        this.setRecordingJirecon(state, token, callback);
29
+    } else {
30
+        this.setRecordingColibri(state, token, callback);
31
+    }
32
+}
33
+
34
+function setRecordingJirecon(state, token, callback) {
35
+    if (state == recordingEnabled){
36
+        return;
37
+    }
38
+
39
+    var iq = $iq({to: config.hosts.jirecon, type: 'set'})
40
+        .c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
41
+            action: state ? 'start' : 'stop',
42
+            mucjid: connection.emuc.roomjid});
43
+    if (!state){
44
+        iq.attrs({rid: jireconRid});
45
+    }
46
+
47
+    console.log('Start recording');
48
+
49
+    connection.sendIQ(
50
+        iq,
51
+        function (result) {
52
+            // TODO wait for an IQ with the real status, since this is
53
+            // provisional?
54
+            jireconRid = $(result).find('recording').attr('rid');
55
+            console.log('Recording ' + (state ? 'started' : 'stopped') +
56
+                '(jirecon)' + result);
57
+            recordingEnabled = state;
58
+            if (!state){
59
+                jireconRid = null;
60
+            }
61
+
62
+            callback(state);
63
+        },
64
+        function (error) {
65
+            console.log('Failed to start recording, error: ', error);
66
+            callback(recordingEnabled);
67
+        });
68
+}
69
+
70
+// Sends a COLIBRI message which enables or disables (according to 'state')
71
+// the recording on the bridge. Waits for the result IQ and calls 'callback'
72
+// with the new recording state, according to the IQ.
73
+function setRecordingColibri(state, token, callback) {
74
+    var elem = $iq({to: focusMucJid, type: 'set'});
75
+    elem.c('conference', {
76
+        xmlns: 'http://jitsi.org/protocol/colibri'
77
+    });
78
+    elem.c('recording', {state: state, token: token});
79
+
80
+    connection.sendIQ(elem,
81
+        function (result) {
82
+            console.log('Set recording "', state, '". Result:', result);
83
+            var recordingElem = $(result).find('>conference>recording');
84
+            var newState = ('true' === recordingElem.attr('state'));
85
+
86
+            recordingEnabled = newState;
87
+            callback(newState);
88
+        },
89
+        function (error) {
90
+            console.warn(error);
91
+            callback(recordingEnabled);
92
+        }
93
+    );
94
+}
95
+
96
+var Recording = {
97
+    toggleRecording: function (tokenEmptyCallback,
98
+                               startingCallback, startedCallback) {
99
+        if (!Moderator.isModerator()) {
100
+            console.log(
101
+                    'non-focus, or conference not yet organized:' +
102
+                    ' not enabling recording');
103
+            return;
104
+        }
105
+
106
+        // Jirecon does not (currently) support a token.
107
+        if (!recordingToken && !useJirecon) {
108
+            tokenEmptyCallback(function (value) {
109
+                setRecordingToken(value);
110
+                this.toggleRecording();
111
+            });
112
+
113
+            return;
114
+        }
115
+
116
+        var oldState = recordingEnabled;
117
+        startingCallback(!oldState);
118
+        setRecording(!oldState,
119
+            recordingToken,
120
+            function (state) {
121
+                console.log("New recording state: ", state);
122
+                if (state === oldState) {
123
+                    // FIXME: new focus:
124
+                    // this will not work when moderator changes
125
+                    // during active session. Then it will assume that
126
+                    // recording status has changed to true, but it might have
127
+                    // been already true(and we only received actual status from
128
+                    // the focus).
129
+                    //
130
+                    // SO we start with status null, so that it is initialized
131
+                    // here and will fail only after second click, so if invalid
132
+                    // token was used we have to press the button twice before
133
+                    // current status will be fetched and token will be reset.
134
+                    //
135
+                    // Reliable way would be to return authentication error.
136
+                    // Or status update when moderator connects.
137
+                    // Or we have to stop recording session when current
138
+                    // moderator leaves the room.
139
+
140
+                    // Failed to change, reset the token because it might
141
+                    // have been wrong
142
+                    setRecordingToken(null);
143
+                }
144
+                startedCallback(state);
145
+
146
+            }
147
+        );
148
+    }
149
+
150
+}
151
+
152
+module.exports = Recording;

+ 607
- 0
modules/xmpp/strophe.emuc.js View File

1
+/* jshint -W117 */
2
+/* a simple MUC connection plugin
3
+ * can only handle a single MUC room
4
+ */
5
+
6
+var bridgeIsDown = false;
7
+
8
+var Moderator = require("./moderator");
9
+
10
+module.exports = function(XMPP, eventEmitter) {
11
+    Strophe.addConnectionPlugin('emuc', {
12
+        connection: null,
13
+        roomjid: null,
14
+        myroomjid: null,
15
+        members: {},
16
+        list_members: [], // so we can elect a new focus
17
+        presMap: {},
18
+        preziMap: {},
19
+        joined: false,
20
+        isOwner: false,
21
+        role: null,
22
+        init: function (conn) {
23
+            this.connection = conn;
24
+        },
25
+        initPresenceMap: function (myroomjid) {
26
+            this.presMap['to'] = myroomjid;
27
+            this.presMap['xns'] = 'http://jabber.org/protocol/muc';
28
+        },
29
+        doJoin: function (jid, password) {
30
+            this.myroomjid = jid;
31
+
32
+            console.info("Joined MUC as " + this.myroomjid);
33
+
34
+            this.initPresenceMap(this.myroomjid);
35
+
36
+            if (!this.roomjid) {
37
+                this.roomjid = Strophe.getBareJidFromJid(jid);
38
+                // add handlers (just once)
39
+                this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, this.roomjid, {matchBare: true});
40
+                this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null, this.roomjid, {matchBare: true});
41
+                this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null, this.roomjid, {matchBare: true});
42
+                this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null, this.roomjid, {matchBare: true});
43
+            }
44
+            if (password !== undefined) {
45
+                this.presMap['password'] = password;
46
+            }
47
+            this.sendPresence();
48
+        },
49
+        doLeave: function () {
50
+            console.log("do leave", this.myroomjid);
51
+            var pres = $pres({to: this.myroomjid, type: 'unavailable' });
52
+            this.presMap.length = 0;
53
+            this.connection.send(pres);
54
+        },
55
+        createNonAnonymousRoom: function () {
56
+            // http://xmpp.org/extensions/xep-0045.html#createroom-reserved
57
+
58
+            var getForm = $iq({type: 'get', to: this.roomjid})
59
+                .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'})
60
+                .c('x', {xmlns: 'jabber:x:data', type: 'submit'});
61
+
62
+            this.connection.sendIQ(getForm, function (form) {
63
+
64
+                if (!$(form).find(
65
+                        '>query>x[xmlns="jabber:x:data"]' +
66
+                        '>field[var="muc#roomconfig_whois"]').length) {
67
+
68
+                    console.error('non-anonymous rooms not supported');
69
+                    return;
70
+                }
71
+
72
+                var formSubmit = $iq({to: this.roomjid, type: 'set'})
73
+                    .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
74
+
75
+                formSubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
76
+
77
+                formSubmit.c('field', {'var': 'FORM_TYPE'})
78
+                    .c('value')
79
+                    .t('http://jabber.org/protocol/muc#roomconfig').up().up();
80
+
81
+                formSubmit.c('field', {'var': 'muc#roomconfig_whois'})
82
+                    .c('value').t('anyone').up().up();
83
+
84
+                this.connection.sendIQ(formSubmit);
85
+
86
+            }, function (error) {
87
+                console.error("Error getting room configuration form");
88
+            });
89
+        },
90
+        onPresence: function (pres) {
91
+            var from = pres.getAttribute('from');
92
+
93
+            // What is this for? A workaround for something?
94
+            if (pres.getAttribute('type')) {
95
+                return true;
96
+            }
97
+
98
+            // Parse etherpad tag.
99
+            var etherpad = $(pres).find('>etherpad');
100
+            if (etherpad.length) {
101
+                if (config.etherpad_base && !Moderator.isModerator()) {
102
+                    UI.initEtherpad(etherpad.text());
103
+                }
104
+            }
105
+
106
+            // Parse prezi tag.
107
+            var presentation = $(pres).find('>prezi');
108
+            if (presentation.length) {
109
+                var url = presentation.attr('url');
110
+                var current = presentation.find('>current').text();
111
+
112
+                console.log('presentation info received from', from, url);
113
+
114
+                if (this.preziMap[from] == null) {
115
+                    this.preziMap[from] = url;
116
+
117
+                    $(document).trigger('presentationadded.muc', [from, url, current]);
118
+                }
119
+                else {
120
+                    $(document).trigger('gotoslide.muc', [from, url, current]);
121
+                }
122
+            }
123
+            else if (this.preziMap[from] != null) {
124
+                var url = this.preziMap[from];
125
+                delete this.preziMap[from];
126
+                $(document).trigger('presentationremoved.muc', [from, url]);
127
+            }
128
+
129
+            // Parse audio info tag.
130
+            var audioMuted = $(pres).find('>audiomuted');
131
+            if (audioMuted.length) {
132
+                $(document).trigger('audiomuted.muc', [from, audioMuted.text()]);
133
+            }
134
+
135
+            // Parse video info tag.
136
+            var videoMuted = $(pres).find('>videomuted');
137
+            if (videoMuted.length) {
138
+                $(document).trigger('videomuted.muc', [from, videoMuted.text()]);
139
+            }
140
+
141
+            var stats = $(pres).find('>stats');
142
+            if (stats.length) {
143
+                var statsObj = {};
144
+                Strophe.forEachChild(stats[0], "stat", function (el) {
145
+                    statsObj[el.getAttribute("name")] = el.getAttribute("value");
146
+                });
147
+                connectionquality.updateRemoteStats(from, statsObj);
148
+            }
149
+
150
+            // Parse status.
151
+            if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) {
152
+                this.isOwner = true;
153
+                this.createNonAnonymousRoom();
154
+            }
155
+
156
+            // Parse roles.
157
+            var member = {};
158
+            member.show = $(pres).find('>show').text();
159
+            member.status = $(pres).find('>status').text();
160
+            var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item');
161
+            member.affiliation = tmp.attr('affiliation');
162
+            member.role = tmp.attr('role');
163
+
164
+            // Focus recognition
165
+            member.jid = tmp.attr('jid');
166
+            member.isFocus = false;
167
+            if (member.jid
168
+                && member.jid.indexOf(Moderator.getFocusUserJid() + "/") == 0) {
169
+                member.isFocus = true;
170
+            }
171
+
172
+            var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]');
173
+            member.displayName = (nicktag.length > 0 ? nicktag.html() : null);
174
+
175
+            if (from == this.myroomjid) {
176
+                if (member.affiliation == 'owner') this.isOwner = true;
177
+                if (this.role !== member.role) {
178
+                    this.role = member.role;
179
+                    if (Moderator.onLocalRoleChange)
180
+                        Moderator.onLocalRoleChange(from, member, pres);
181
+                    UI.onLocalRoleChange(from, member, pres);
182
+                }
183
+                if (!this.joined) {
184
+                    this.joined = true;
185
+                    eventEmitter.emit(XMPPEvents.MUC_JOINED, from, member);
186
+                    this.list_members.push(from);
187
+                }
188
+            } else if (this.members[from] === undefined) {
189
+                // new participant
190
+                this.members[from] = member;
191
+                this.list_members.push(from);
192
+                console.log('entered', from, member);
193
+                if (member.isFocus) {
194
+                    focusMucJid = from;
195
+                    console.info("Ignore focus: " + from + ", real JID: " + member.jid);
196
+                }
197
+                else {
198
+                    var id = $(pres).find('>userID').text();
199
+                    var email = $(pres).find('>email');
200
+                    if (email.length > 0) {
201
+                        id = email.text();
202
+                    }
203
+                    UI.onMucEntered(from, id, member.displayName);
204
+                    API.triggerEvent("participantJoined", {jid: from});
205
+                }
206
+            } else {
207
+                // Presence update for existing participant
208
+                // Watch role change:
209
+                if (this.members[from].role != member.role) {
210
+                    this.members[from].role = member.role;
211
+                    UI.onMucRoleChanged(member.role, member.displayName);
212
+                }
213
+            }
214
+
215
+            // Always trigger presence to update bindings
216
+            $(document).trigger('presence.muc', [from, member, pres]);
217
+            this.parsePresence(from, member, pres);
218
+
219
+            // Trigger status message update
220
+            if (member.status) {
221
+                UI.onMucPresenceStatus(from, member);
222
+            }
223
+
224
+            return true;
225
+        },
226
+        onPresenceUnavailable: function (pres) {
227
+            var from = pres.getAttribute('from');
228
+            // Status code 110 indicates that this notification is "self-presence".
229
+            if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) {
230
+                delete this.members[from];
231
+                this.list_members.splice(this.list_members.indexOf(from), 1);
232
+                this.onParticipantLeft(from);
233
+            }
234
+            // If the status code is 110 this means we're leaving and we would like
235
+            // to remove everyone else from our view, so we trigger the event.
236
+            else if (this.list_members.length > 1) {
237
+                for (var i = 0; i < this.list_members.length; i++) {
238
+                    var member = this.list_members[i];
239
+                    delete this.members[i];
240
+                    this.list_members.splice(i, 1);
241
+                    this.onParticipantLeft(member);
242
+                }
243
+            }
244
+            if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) {
245
+                $(document).trigger('kicked.muc', [from]);
246
+                if (this.myroomjid === from) {
247
+                    XMPP.disposeConference(false);
248
+                    eventEmitter.emit(XMPPEvents.KICKED);
249
+                }
250
+            }
251
+            return true;
252
+        },
253
+        onPresenceError: function (pres) {
254
+            var from = pres.getAttribute('from');
255
+            if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
256
+                console.log('on password required', from);
257
+                var self = this;
258
+                UI.onPasswordReqiured(function (value) {
259
+                    self.doJoin(from, value);
260
+                });
261
+            } else if ($(pres).find(
262
+                '>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
263
+                var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));
264
+                if (toDomain === config.hosts.anonymousdomain) {
265
+                    // we are connected with anonymous domain and only non anonymous users can create rooms
266
+                    // we must authorize the user
267
+                    XMPP.promptLogin();
268
+                } else {
269
+                    console.warn('onPresError ', pres);
270
+                    UI.messageHandler.openReportDialog(null,
271
+                        'Oops! Something went wrong and we couldn`t connect to the conference.',
272
+                        pres);
273
+                }
274
+            } else {
275
+                console.warn('onPresError ', pres);
276
+                UI.messageHandler.openReportDialog(null,
277
+                    'Oops! Something went wrong and we couldn`t connect to the conference.',
278
+                    pres);
279
+            }
280
+            return true;
281
+        },
282
+        sendMessage: function (body, nickname) {
283
+            var msg = $msg({to: this.roomjid, type: 'groupchat'});
284
+            msg.c('body', body).up();
285
+            if (nickname) {
286
+                msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up();
287
+            }
288
+            this.connection.send(msg);
289
+            API.triggerEvent("outgoingMessage", {"message": body});
290
+        },
291
+        setSubject: function (subject) {
292
+            var msg = $msg({to: this.roomjid, type: 'groupchat'});
293
+            msg.c('subject', subject);
294
+            this.connection.send(msg);
295
+            console.log("topic changed to " + subject);
296
+        },
297
+        onMessage: function (msg) {
298
+            // FIXME: this is a hack. but jingle on muc makes nickchanges hard
299
+            var from = msg.getAttribute('from');
300
+            var nick = $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]').text() || Strophe.getResourceFromJid(from);
301
+
302
+            var txt = $(msg).find('>body').text();
303
+            var type = msg.getAttribute("type");
304
+            if (type == "error") {
305
+                UI.chatAddError($(msg).find('>text').text(), txt);
306
+                return true;
307
+            }
308
+
309
+            var subject = $(msg).find('>subject');
310
+            if (subject.length) {
311
+                var subjectText = subject.text();
312
+                if (subjectText || subjectText == "") {
313
+                    UI.chatSetSubject(subjectText);
314
+                    console.log("Subject is changed to " + subjectText);
315
+                }
316
+            }
317
+
318
+
319
+            if (txt) {
320
+                console.log('chat', nick, txt);
321
+                UI.updateChatConversation(from, nick, txt);
322
+                if (from != this.myroomjid)
323
+                    API.triggerEvent("incomingMessage",
324
+                        {"from": from, "nick": nick, "message": txt});
325
+            }
326
+            return true;
327
+        },
328
+        lockRoom: function (key, onSuccess, onError, onNotSupported) {
329
+            //http://xmpp.org/extensions/xep-0045.html#roomconfig
330
+            var ob = this;
331
+            this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}),
332
+                function (res) {
333
+                    if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) {
334
+                        var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
335
+                        formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
336
+                        formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up();
337
+                        formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up();
338
+                        // Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373
339
+                        formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up();
340
+                        // FIXME: is muc#roomconfig_passwordprotectedroom required?
341
+                        this.connection.sendIQ(formsubmit,
342
+                            onSuccess,
343
+                            onError);
344
+                    } else {
345
+                        onNotSupported();
346
+                    }
347
+                }, onError);
348
+        },
349
+        kick: function (jid) {
350
+            var kickIQ = $iq({to: this.roomjid, type: 'set'})
351
+                .c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'})
352
+                .c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'})
353
+                .c('reason').t('You have been kicked.').up().up().up();
354
+
355
+            this.connection.sendIQ(
356
+                kickIQ,
357
+                function (result) {
358
+                    console.log('Kick participant with jid: ', jid, result);
359
+                },
360
+                function (error) {
361
+                    console.log('Kick participant error: ', error);
362
+                });
363
+        },
364
+        sendPresence: function () {
365
+            var pres = $pres({to: this.presMap['to'] });
366
+            pres.c('x', {xmlns: this.presMap['xns']});
367
+
368
+            if (this.presMap['password']) {
369
+                pres.c('password').t(this.presMap['password']).up();
370
+            }
371
+
372
+            pres.up();
373
+
374
+            // Send XEP-0115 'c' stanza that contains our capabilities info
375
+            if (this.connection.caps) {
376
+                this.connection.caps.node = config.clientNode;
377
+                pres.c('c', this.connection.caps.generateCapsAttrs()).up();
378
+            }
379
+
380
+            pres.c('user-agent', {xmlns: 'http://jitsi.org/jitmeet/user-agent'})
381
+                .t(navigator.userAgent).up();
382
+
383
+            if (this.presMap['bridgeIsDown']) {
384
+                pres.c('bridgeIsDown').up();
385
+            }
386
+
387
+            if (this.presMap['email']) {
388
+                pres.c('email').t(this.presMap['email']).up();
389
+            }
390
+
391
+            if (this.presMap['userId']) {
392
+                pres.c('userId').t(this.presMap['userId']).up();
393
+            }
394
+
395
+            if (this.presMap['displayName']) {
396
+                // XEP-0172
397
+                pres.c('nick', {xmlns: 'http://jabber.org/protocol/nick'})
398
+                    .t(this.presMap['displayName']).up();
399
+            }
400
+
401
+            if (this.presMap['audions']) {
402
+                pres.c('audiomuted', {xmlns: this.presMap['audions']})
403
+                    .t(this.presMap['audiomuted']).up();
404
+            }
405
+
406
+            if (this.presMap['videons']) {
407
+                pres.c('videomuted', {xmlns: this.presMap['videons']})
408
+                    .t(this.presMap['videomuted']).up();
409
+            }
410
+
411
+            if (this.presMap['statsns']) {
412
+                var stats = pres.c('stats', {xmlns: this.presMap['statsns']});
413
+                for (var stat in this.presMap["stats"])
414
+                    if (this.presMap["stats"][stat] != null)
415
+                        stats.c("stat", {name: stat, value: this.presMap["stats"][stat]}).up();
416
+                pres.up();
417
+            }
418
+
419
+            if (this.presMap['prezins']) {
420
+                pres.c('prezi',
421
+                    {xmlns: this.presMap['prezins'],
422
+                        'url': this.presMap['preziurl']})
423
+                    .c('current').t(this.presMap['prezicurrent']).up().up();
424
+            }
425
+
426
+            if (this.presMap['etherpadns']) {
427
+                pres.c('etherpad', {xmlns: this.presMap['etherpadns']})
428
+                    .t(this.presMap['etherpadname']).up();
429
+            }
430
+
431
+            if (this.presMap['medians']) {
432
+                pres.c('media', {xmlns: this.presMap['medians']});
433
+                var sourceNumber = 0;
434
+                Object.keys(this.presMap).forEach(function (key) {
435
+                    if (key.indexOf('source') >= 0) {
436
+                        sourceNumber++;
437
+                    }
438
+                });
439
+                if (sourceNumber > 0)
440
+                    for (var i = 1; i <= sourceNumber / 3; i++) {
441
+                        pres.c('source',
442
+                            {type: this.presMap['source' + i + '_type'],
443
+                                ssrc: this.presMap['source' + i + '_ssrc'],
444
+                                direction: this.presMap['source' + i + '_direction']
445
+                                    || 'sendrecv' }
446
+                        ).up();
447
+                    }
448
+            }
449
+
450
+            pres.up();
451
+//        console.debug(pres.toString());
452
+            this.connection.send(pres);
453
+        },
454
+        addDisplayNameToPresence: function (displayName) {
455
+            this.presMap['displayName'] = displayName;
456
+        },
457
+        addMediaToPresence: function (sourceNumber, mtype, ssrcs, direction) {
458
+            if (!this.presMap['medians'])
459
+                this.presMap['medians'] = 'http://estos.de/ns/mjs';
460
+
461
+            this.presMap['source' + sourceNumber + '_type'] = mtype;
462
+            this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
463
+            this.presMap['source' + sourceNumber + '_direction'] = direction;
464
+        },
465
+        clearPresenceMedia: function () {
466
+            var self = this;
467
+            Object.keys(this.presMap).forEach(function (key) {
468
+                if (key.indexOf('source') != -1) {
469
+                    delete self.presMap[key];
470
+                }
471
+            });
472
+        },
473
+        addPreziToPresence: function (url, currentSlide) {
474
+            this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
475
+            this.presMap['preziurl'] = url;
476
+            this.presMap['prezicurrent'] = currentSlide;
477
+        },
478
+        removePreziFromPresence: function () {
479
+            delete this.presMap['prezins'];
480
+            delete this.presMap['preziurl'];
481
+            delete this.presMap['prezicurrent'];
482
+        },
483
+        addCurrentSlideToPresence: function (currentSlide) {
484
+            this.presMap['prezicurrent'] = currentSlide;
485
+        },
486
+        getPrezi: function (roomjid) {
487
+            return this.preziMap[roomjid];
488
+        },
489
+        addEtherpadToPresence: function (etherpadName) {
490
+            this.presMap['etherpadns'] = 'http://jitsi.org/jitmeet/etherpad';
491
+            this.presMap['etherpadname'] = etherpadName;
492
+        },
493
+        addAudioInfoToPresence: function (isMuted) {
494
+            this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio';
495
+            this.presMap['audiomuted'] = isMuted.toString();
496
+        },
497
+        addVideoInfoToPresence: function (isMuted) {
498
+            this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
499
+            this.presMap['videomuted'] = isMuted.toString();
500
+        },
501
+        addConnectionInfoToPresence: function (stats) {
502
+            this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats';
503
+            this.presMap['stats'] = stats;
504
+        },
505
+        findJidFromResource: function (resourceJid) {
506
+            if (resourceJid &&
507
+                resourceJid === Strophe.getResourceFromJid(this.myroomjid)) {
508
+                return this.myroomjid;
509
+            }
510
+            var peerJid = null;
511
+            Object.keys(this.members).some(function (jid) {
512
+                peerJid = jid;
513
+                return Strophe.getResourceFromJid(jid) === resourceJid;
514
+            });
515
+            return peerJid;
516
+        },
517
+        addBridgeIsDownToPresence: function () {
518
+            this.presMap['bridgeIsDown'] = true;
519
+        },
520
+        addEmailToPresence: function (email) {
521
+            this.presMap['email'] = email;
522
+        },
523
+        addUserIdToPresence: function (userId) {
524
+            this.presMap['userId'] = userId;
525
+        },
526
+        isModerator: function () {
527
+            return this.role === 'moderator';
528
+        },
529
+        getMemberRole: function (peerJid) {
530
+            if (this.members[peerJid]) {
531
+                return this.members[peerJid].role;
532
+            }
533
+            return null;
534
+        },
535
+        onParticipantLeft: function (jid) {
536
+            UI.onMucLeft(jid);
537
+
538
+            API.triggerEvent("participantLeft", {jid: jid});
539
+
540
+            delete jid2Ssrc[jid];
541
+
542
+            this.connection.jingle.terminateByJid(jid);
543
+
544
+            if (this.getPrezi(jid)) {
545
+                $(document).trigger('presentationremoved.muc',
546
+                    [jid, this.getPrezi(jid)]);
547
+            }
548
+
549
+            Moderator.onMucLeft(jid);
550
+        },
551
+        parsePresence: function (from, memeber, pres) {
552
+            if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
553
+                bridgeIsDown = true;
554
+                eventEmitter.emit(XMPPEvents.BRIDGE_DOWN);
555
+            }
556
+
557
+            if(memeber.isFocus)
558
+                return;
559
+
560
+            // Remove old ssrcs coming from the jid
561
+            Object.keys(ssrc2jid).forEach(function (ssrc) {
562
+                if (ssrc2jid[ssrc] == jid) {
563
+                    delete ssrc2jid[ssrc];
564
+                    delete ssrc2videoType[ssrc];
565
+                }
566
+            });
567
+
568
+            var changedStreams = [];
569
+            $(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
570
+                //console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
571
+                var ssrcV = ssrc.getAttribute('ssrc');
572
+                ssrc2jid[ssrcV] = from;
573
+                notReceivedSSRCs.push(ssrcV);
574
+
575
+                var type = ssrc.getAttribute('type');
576
+                ssrc2videoType[ssrcV] = type;
577
+
578
+                var direction = ssrc.getAttribute('direction');
579
+
580
+                changedStreams.push({type: type, direction: direction});
581
+
582
+            });
583
+
584
+            eventEmitter.emit(XMPPEvents.CHANGED_STREAMS, from, changedStreams);
585
+
586
+            var displayName = !config.displayJids
587
+                ? memeber.displayName : Strophe.getResourceFromJid(from);
588
+
589
+            if (displayName && displayName.length > 0)
590
+            {
591
+//                $(document).trigger('displaynamechanged',
592
+//                    [jid, displayName]);
593
+                eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName);
594
+            }
595
+
596
+
597
+            var id = $(pres).find('>userID').text();
598
+            var email = $(pres).find('>email');
599
+            if(email.length > 0) {
600
+                id = email.text();
601
+            }
602
+
603
+            eventEmitter.emit(XMPPEvents.USER_ID_CHANGED, from, id);
604
+        }
605
+    });
606
+};
607
+

+ 334
- 0
modules/xmpp/strophe.jingle.js View File

1
+/* jshint -W117 */
2
+
3
+var JingleSession = require("./JingleSession");
4
+
5
+function CallIncomingJingle(sid, connection) {
6
+    var sess = connection.jingle.sessions[sid];
7
+
8
+    // TODO: do we check activecall == null?
9
+    activecall = sess;
10
+
11
+    statistics.onConferenceCreated(sess);
12
+    RTC.onConferenceCreated(sess);
13
+
14
+    // TODO: check affiliation and/or role
15
+    console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
16
+    sess.usedrip = true; // not-so-naive trickle ice
17
+    sess.sendAnswer();
18
+    sess.accept();
19
+
20
+};
21
+
22
+module.exports = function(XMPP)
23
+{
24
+    Strophe.addConnectionPlugin('jingle', {
25
+        connection: null,
26
+        sessions: {},
27
+        jid2session: {},
28
+        ice_config: {iceServers: []},
29
+        pc_constraints: {},
30
+        media_constraints: {
31
+            mandatory: {
32
+                'OfferToReceiveAudio': true,
33
+                'OfferToReceiveVideo': true
34
+            }
35
+            // MozDontOfferDataChannel: true when this is firefox
36
+        },
37
+        init: function (conn) {
38
+            this.connection = conn;
39
+            if (this.connection.disco) {
40
+                // http://xmpp.org/extensions/xep-0167.html#support
41
+                // http://xmpp.org/extensions/xep-0176.html#support
42
+                this.connection.disco.addFeature('urn:xmpp:jingle:1');
43
+                this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
44
+                this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
45
+                this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
46
+                this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video');
47
+
48
+
49
+                // this is dealt with by SDP O/A so we don't need to annouce this
50
+                //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
51
+                //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
52
+                if (config.useRtcpMux) {
53
+                    this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
54
+                }
55
+                if (config.useBundle) {
56
+                    this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
57
+                }
58
+                //this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
59
+            }
60
+            this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
61
+        },
62
+        onJingle: function (iq) {
63
+            var sid = $(iq).find('jingle').attr('sid');
64
+            var action = $(iq).find('jingle').attr('action');
65
+            var fromJid = iq.getAttribute('from');
66
+            // send ack first
67
+            var ack = $iq({type: 'result',
68
+                to: fromJid,
69
+                id: iq.getAttribute('id')
70
+            });
71
+            console.log('on jingle ' + action + ' from ' + fromJid, iq);
72
+            var sess = this.sessions[sid];
73
+            if ('session-initiate' != action) {
74
+                if (sess === null) {
75
+                    ack.type = 'error';
76
+                    ack.c('error', {type: 'cancel'})
77
+                        .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
78
+                        .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
79
+                    this.connection.send(ack);
80
+                    return true;
81
+                }
82
+                // compare from to sess.peerjid (bare jid comparison for later compat with message-mode)
83
+                // local jid is not checked
84
+                if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) {
85
+                    console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
86
+                    ack.type = 'error';
87
+                    ack.c('error', {type: 'cancel'})
88
+                        .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
89
+                        .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
90
+                    this.connection.send(ack);
91
+                    return true;
92
+                }
93
+            } else if (sess !== undefined) {
94
+                // existing session with same session id
95
+                // this might be out-of-order if the sess.peerjid is the same as from
96
+                ack.type = 'error';
97
+                ack.c('error', {type: 'cancel'})
98
+                    .c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
99
+                console.warn('duplicate session id', sid);
100
+                this.connection.send(ack);
101
+                return true;
102
+            }
103
+            // FIXME: check for a defined action
104
+            this.connection.send(ack);
105
+            // see http://xmpp.org/extensions/xep-0166.html#concepts-session
106
+            switch (action) {
107
+                case 'session-initiate':
108
+                    sess = new JingleSession(
109
+                        $(iq).attr('to'), $(iq).find('jingle').attr('sid'),
110
+                        this.connection, XMPP);
111
+                    // configure session
112
+
113
+                    sess.media_constraints = this.media_constraints;
114
+                    sess.pc_constraints = this.pc_constraints;
115
+                    sess.ice_config = this.ice_config;
116
+
117
+                    sess.initiate(fromJid, false);
118
+                    // FIXME: setRemoteDescription should only be done when this call is to be accepted
119
+                    sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
120
+
121
+                    this.sessions[sess.sid] = sess;
122
+                    this.jid2session[sess.peerjid] = sess;
123
+
124
+                    // the callback should either
125
+                    // .sendAnswer and .accept
126
+                    // or .sendTerminate -- not necessarily synchronus
127
+                    CallIncomingJingle(sess.sid, this.connection);
128
+                    break;
129
+                case 'session-accept':
130
+                    sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
131
+                    sess.accept();
132
+                    $(document).trigger('callaccepted.jingle', [sess.sid]);
133
+                    break;
134
+                case 'session-terminate':
135
+                    // If this is not the focus sending the terminate, we have
136
+                    // nothing more to do here.
137
+                    if (Object.keys(this.sessions).length < 1
138
+                        || !(this.sessions[Object.keys(this.sessions)[0]]
139
+                            instanceof JingleSession))
140
+                    {
141
+                        break;
142
+                    }
143
+                    console.log('terminating...', sess.sid);
144
+                    sess.terminate();
145
+                    this.terminate(sess.sid);
146
+                    if ($(iq).find('>jingle>reason').length) {
147
+                        $(document).trigger('callterminated.jingle', [
148
+                            sess.sid,
149
+                            sess.peerjid,
150
+                            $(iq).find('>jingle>reason>:first')[0].tagName,
151
+                            $(iq).find('>jingle>reason>text').text()
152
+                        ]);
153
+                    } else {
154
+                        $(document).trigger('callterminated.jingle',
155
+                            [sess.sid, sess.peerjid]);
156
+                    }
157
+                    break;
158
+                case 'transport-info':
159
+                    sess.addIceCandidate($(iq).find('>jingle>content'));
160
+                    break;
161
+                case 'session-info':
162
+                    var affected;
163
+                    if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
164
+                        $(document).trigger('ringing.jingle', [sess.sid]);
165
+                    } else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
166
+                        affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
167
+                        $(document).trigger('mute.jingle', [sess.sid, affected]);
168
+                    } else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
169
+                        affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
170
+                        $(document).trigger('unmute.jingle', [sess.sid, affected]);
171
+                    }
172
+                    break;
173
+                case 'addsource': // FIXME: proprietary, un-jingleish
174
+                case 'source-add': // FIXME: proprietary
175
+                    sess.addSource($(iq).find('>jingle>content'), fromJid);
176
+                    break;
177
+                case 'removesource': // FIXME: proprietary, un-jingleish
178
+                case 'source-remove': // FIXME: proprietary
179
+                    sess.removeSource($(iq).find('>jingle>content'), fromJid);
180
+                    break;
181
+                default:
182
+                    console.warn('jingle action not implemented', action);
183
+                    break;
184
+            }
185
+            return true;
186
+        },
187
+        initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid
188
+            var sess = new JingleSession(myjid || this.connection.jid,
189
+                Math.random().toString(36).substr(2, 12), // random string
190
+                this.connection, XMPP);
191
+            // configure session
192
+
193
+            sess.media_constraints = this.media_constraints;
194
+            sess.pc_constraints = this.pc_constraints;
195
+            sess.ice_config = this.ice_config;
196
+
197
+            sess.initiate(peerjid, true);
198
+            this.sessions[sess.sid] = sess;
199
+            this.jid2session[sess.peerjid] = sess;
200
+            sess.sendOffer();
201
+            return sess;
202
+        },
203
+        terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
204
+            if (sid === null || sid === undefined) {
205
+                for (sid in this.sessions) {
206
+                    if (this.sessions[sid].state != 'ended') {
207
+                        this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
208
+                        this.sessions[sid].terminate();
209
+                    }
210
+                    delete this.jid2session[this.sessions[sid].peerjid];
211
+                    delete this.sessions[sid];
212
+                }
213
+            } else if (this.sessions.hasOwnProperty(sid)) {
214
+                if (this.sessions[sid].state != 'ended') {
215
+                    this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
216
+                    this.sessions[sid].terminate();
217
+                }
218
+                delete this.jid2session[this.sessions[sid].peerjid];
219
+                delete this.sessions[sid];
220
+            }
221
+        },
222
+        // Used to terminate a session when an unavailable presence is received.
223
+        terminateByJid: function (jid) {
224
+            if (this.jid2session.hasOwnProperty(jid)) {
225
+                var sess = this.jid2session[jid];
226
+                if (sess) {
227
+                    sess.terminate();
228
+                    console.log('peer went away silently', jid);
229
+                    delete this.sessions[sess.sid];
230
+                    delete this.jid2session[jid];
231
+                    $(document).trigger('callterminated.jingle',
232
+                        [sess.sid, jid], 'gone');
233
+                }
234
+            }
235
+        },
236
+        terminateRemoteByJid: function (jid, reason) {
237
+            if (this.jid2session.hasOwnProperty(jid)) {
238
+                var sess = this.jid2session[jid];
239
+                if (sess) {
240
+                    sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null);
241
+                    sess.terminate();
242
+                    console.log('terminate peer with jid', sess.sid, jid);
243
+                    delete this.sessions[sess.sid];
244
+                    delete this.jid2session[jid];
245
+                    $(document).trigger('callterminated.jingle',
246
+                        [sess.sid, jid, 'kicked']);
247
+                }
248
+            }
249
+        },
250
+        getStunAndTurnCredentials: function () {
251
+            // get stun and turn configuration from server via xep-0215
252
+            // uses time-limited credentials as described in
253
+            // http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
254
+            //
255
+            // see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua
256
+            // for a prosody module which implements this
257
+            //
258
+            // currently, this doesn't work with updateIce and therefore credentials with a long
259
+            // validity have to be fetched before creating the peerconnection
260
+            // TODO: implement refresh via updateIce as described in
261
+            //      https://code.google.com/p/webrtc/issues/detail?id=1650
262
+            var self = this;
263
+            this.connection.sendIQ(
264
+                $iq({type: 'get', to: this.connection.domain})
265
+                    .c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}),
266
+                function (res) {
267
+                    var iceservers = [];
268
+                    $(res).find('>services>service').each(function (idx, el) {
269
+                        el = $(el);
270
+                        var dict = {};
271
+                        var type = el.attr('type');
272
+                        switch (type) {
273
+                            case 'stun':
274
+                                dict.url = 'stun:' + el.attr('host');
275
+                                if (el.attr('port')) {
276
+                                    dict.url += ':' + el.attr('port');
277
+                                }
278
+                                iceservers.push(dict);
279
+                                break;
280
+                            case 'turn':
281
+                            case 'turns':
282
+                                dict.url = type + ':';
283
+                                if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508
284
+                                    if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) {
285
+                                        dict.url += el.attr('username') + '@';
286
+                                    } else {
287
+                                        dict.username = el.attr('username'); // only works in M28
288
+                                    }
289
+                                }
290
+                                dict.url += el.attr('host');
291
+                                if (el.attr('port') && el.attr('port') != '3478') {
292
+                                    dict.url += ':' + el.attr('port');
293
+                                }
294
+                                if (el.attr('transport') && el.attr('transport') != 'udp') {
295
+                                    dict.url += '?transport=' + el.attr('transport');
296
+                                }
297
+                                if (el.attr('password')) {
298
+                                    dict.credential = el.attr('password');
299
+                                }
300
+                                iceservers.push(dict);
301
+                                break;
302
+                        }
303
+                    });
304
+                    self.ice_config.iceServers = iceservers;
305
+                },
306
+                function (err) {
307
+                    console.warn('getting turn credentials failed', err);
308
+                    console.warn('is mod_turncredentials or similar installed?');
309
+                }
310
+            );
311
+            // implement push?
312
+        },
313
+
314
+        /**
315
+         * Populates the log data
316
+         */
317
+        populateData: function () {
318
+            var data = {};
319
+            Object.keys(this.sessions).forEach(function (sid) {
320
+                var session = this.sessions[sid];
321
+                if (session.peerconnection && session.peerconnection.updateLog) {
322
+                    // FIXME: should probably be a .dump call
323
+                    data["jingle_" + session.sid] = {
324
+                        updateLog: session.peerconnection.updateLog,
325
+                        stats: session.peerconnection.stats,
326
+                        url: window.location.href
327
+                    };
328
+                }
329
+            });
330
+            return data;
331
+        }
332
+    });
333
+};
334
+

+ 20
- 0
modules/xmpp/strophe.logger.js View File

1
+/* global Strophe */
2
+module.exports = function () {
3
+
4
+    Strophe.addConnectionPlugin('logger', {
5
+        // logs raw stanzas and makes them available for download as JSON
6
+        connection: null,
7
+        log: [],
8
+        init: function (conn) {
9
+            this.connection = conn;
10
+            this.connection.rawInput = this.log_incoming.bind(this);
11
+            this.connection.rawOutput = this.log_outgoing.bind(this);
12
+        },
13
+        log_incoming: function (stanza) {
14
+            this.log.push([new Date().getTime(), 'incoming', stanza]);
15
+        },
16
+        log_outgoing: function (stanza) {
17
+            this.log.push([new Date().getTime(), 'outgoing', stanza]);
18
+        }
19
+    });
20
+};

+ 58
- 0
modules/xmpp/strophe.moderate.js View File

1
+/* global $, $iq, config, connection, focusMucJid, forceMuted,
2
+   setAudioMuted, Strophe */
3
+/**
4
+ * Moderate connection plugin.
5
+ */
6
+module.exports = function (XMPP) {
7
+    Strophe.addConnectionPlugin('moderate', {
8
+        connection: null,
9
+        init: function (conn) {
10
+            this.connection = conn;
11
+
12
+            this.connection.addHandler(this.onMute.bind(this),
13
+                'http://jitsi.org/jitmeet/audio',
14
+                'iq',
15
+                'set',
16
+                null,
17
+                null);
18
+        },
19
+        setMute: function (jid, mute) {
20
+            console.info("set mute", mute);
21
+            var iqToFocus = $iq({to: focusMucJid, type: 'set'})
22
+                .c('mute', {
23
+                    xmlns: 'http://jitsi.org/jitmeet/audio',
24
+                    jid: jid
25
+                })
26
+                .t(mute.toString())
27
+                .up();
28
+
29
+            this.connection.sendIQ(
30
+                iqToFocus,
31
+                function (result) {
32
+                    console.log('set mute', result);
33
+                },
34
+                function (error) {
35
+                    console.log('set mute error', error);
36
+                });
37
+        },
38
+        onMute: function (iq) {
39
+            var from = iq.getAttribute('from');
40
+            if (from !== focusMucJid) {
41
+                console.warn("Ignored mute from non focus peer");
42
+                return false;
43
+            }
44
+            var mute = $(iq).find('mute');
45
+            if (mute.length) {
46
+                var doMuteAudio = mute.text() === "true";
47
+                UI.setAudioMuted(doMuteAudio);
48
+                XMPP.forceMuted = doMuteAudio;
49
+            }
50
+            return true;
51
+        },
52
+        eject: function (jid) {
53
+            // We're not the focus, so can't terminate
54
+            //connection.jingle.terminateRemoteByJid(jid, 'kick');
55
+            this.connection.emuc.kick(jid);
56
+        }
57
+    });
58
+}

+ 95
- 0
modules/xmpp/strophe.rayo.js View File

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

+ 42
- 0
modules/xmpp/strophe.util.js View File

1
+/**
2
+ * Strophe logger implementation. Logs from level WARN and above.
3
+ */
4
+module.exports = function () {
5
+
6
+    Strophe.log = function (level, msg) {
7
+        switch (level) {
8
+            case Strophe.LogLevel.WARN:
9
+                console.warn("Strophe: " + msg);
10
+                break;
11
+            case Strophe.LogLevel.ERROR:
12
+            case Strophe.LogLevel.FATAL:
13
+                console.error("Strophe: " + msg);
14
+                break;
15
+        }
16
+    };
17
+
18
+    Strophe.getStatusString = function (status) {
19
+        switch (status) {
20
+            case Strophe.Status.ERROR:
21
+                return "ERROR";
22
+            case Strophe.Status.CONNECTING:
23
+                return "CONNECTING";
24
+            case Strophe.Status.CONNFAIL:
25
+                return "CONNFAIL";
26
+            case Strophe.Status.AUTHENTICATING:
27
+                return "AUTHENTICATING";
28
+            case Strophe.Status.AUTHFAIL:
29
+                return "AUTHFAIL";
30
+            case Strophe.Status.CONNECTED:
31
+                return "CONNECTED";
32
+            case Strophe.Status.DISCONNECTED:
33
+                return "DISCONNECTED";
34
+            case Strophe.Status.DISCONNECTING:
35
+                return "DISCONNECTING";
36
+            case Strophe.Status.ATTACHED:
37
+                return "ATTACHED";
38
+            default:
39
+                return "unknown";
40
+        }
41
+    };
42
+};

+ 422
- 0
modules/xmpp/xmpp.js View File

1
+var Moderator = require("./moderator");
2
+var EventEmitter = require("events");
3
+var Recording = require("./recording");
4
+var SDP = require("./SDP");
5
+
6
+var eventEmitter = new EventEmitter();
7
+var connection = null;
8
+var authenticatedUser = false;
9
+var activecall = null;
10
+
11
+function connect(jid, password, uiCredentials) {
12
+    var bosh
13
+        = uiCredentials.bosh || config.bosh || '/http-bind';
14
+    connection = new Strophe.Connection(bosh);
15
+    Moderator.setConnection(connection);
16
+
17
+    var settings = UI.getSettings();
18
+    var email = settings.email;
19
+    var displayName = settings.displayName;
20
+    if(email) {
21
+        connection.emuc.addEmailToPresence(email);
22
+    } else {
23
+        connection.emuc.addUserIdToPresence(settings.uid);
24
+    }
25
+    if(displayName) {
26
+        connection.emuc.addDisplayNameToPresence(displayName);
27
+    }
28
+
29
+    if (connection.disco) {
30
+        // for chrome, add multistream cap
31
+    }
32
+    connection.jingle.pc_constraints = RTC.getPCConstraints();
33
+    if (config.useIPv6) {
34
+        // https://code.google.com/p/webrtc/issues/detail?id=2828
35
+        if (!connection.jingle.pc_constraints.optional)
36
+            connection.jingle.pc_constraints.optional = [];
37
+        connection.jingle.pc_constraints.optional.push({googIPv6: true});
38
+    }
39
+
40
+    if(!password)
41
+        password = uiCredentials.password;
42
+
43
+    var anonymousConnectionFailed = false;
44
+    connection.connect(jid, password, function (status, msg) {
45
+        console.log('Strophe status changed to',
46
+            Strophe.getStatusString(status));
47
+        if (status === Strophe.Status.CONNECTED) {
48
+            if (config.useStunTurn) {
49
+                connection.jingle.getStunAndTurnCredentials();
50
+            }
51
+            UI.disableConnect();
52
+
53
+            console.info("My Jabber ID: " + connection.jid);
54
+
55
+            if(password)
56
+                authenticatedUser = true;
57
+            maybeDoJoin();
58
+        } else if (status === Strophe.Status.CONNFAIL) {
59
+            if(msg === 'x-strophe-bad-non-anon-jid') {
60
+                anonymousConnectionFailed = true;
61
+            }
62
+        } else if (status === Strophe.Status.DISCONNECTED) {
63
+            if(anonymousConnectionFailed) {
64
+                // prompt user for username and password
65
+                XMPP.promptLogin();
66
+            }
67
+        } else if (status === Strophe.Status.AUTHFAIL) {
68
+            // wrong password or username, prompt user
69
+            XMPP.promptLogin();
70
+
71
+        }
72
+    });
73
+}
74
+
75
+
76
+
77
+function maybeDoJoin() {
78
+    if (connection && connection.connected &&
79
+        Strophe.getResourceFromJid(connection.jid)
80
+        && (RTC.localAudio || RTC.localVideo)) {
81
+        // .connected is true while connecting?
82
+        doJoin();
83
+    }
84
+}
85
+
86
+function doJoin() {
87
+    var roomName = UI.generateRoomName();
88
+
89
+    Moderator.allocateConferenceFocus(
90
+        roomName, UI.checkForNicknameAndJoin);
91
+}
92
+
93
+function initStrophePlugins()
94
+{
95
+    require("./strophe.emuc")(XMPP, eventEmitter);
96
+    require("./strophe.jingle")();
97
+    require("./strophe.moderate")(XMPP);
98
+    require("./strophe.util")();
99
+    require("./strophe.rayo")();
100
+    require("./strophe.logger")();
101
+}
102
+
103
+function registerListeners() {
104
+    RTC.addStreamListener(maybeDoJoin,
105
+        StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
106
+}
107
+
108
+function setupEvents() {
109
+    $(window).bind('beforeunload', function () {
110
+        if (connection && connection.connected) {
111
+            // ensure signout
112
+            $.ajax({
113
+                type: 'POST',
114
+                url: config.bosh,
115
+                async: false,
116
+                cache: false,
117
+                contentType: 'application/xml',
118
+                data: "<body rid='" + (connection.rid || connection._proto.rid)
119
+                    + "' xmlns='http://jabber.org/protocol/httpbind' sid='"
120
+                    + (connection.sid || connection._proto.sid)
121
+                    + "' type='terminate'>" +
122
+                    "<presence xmlns='jabber:client' type='unavailable'/>" +
123
+                    "</body>",
124
+                success: function (data) {
125
+                    console.log('signed out');
126
+                    console.log(data);
127
+                },
128
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
129
+                    console.log('signout error',
130
+                            textStatus + ' (' + errorThrown + ')');
131
+                }
132
+            });
133
+        }
134
+        XMPP.disposeConference(true);
135
+    });
136
+}
137
+
138
+var XMPP = {
139
+    sessionTerminated: false,
140
+    /**
141
+     * Remembers if we were muted by the focus.
142
+     * @type {boolean}
143
+     */
144
+    forceMuted: false,
145
+    start: function (uiCredentials) {
146
+        setupEvents();
147
+        initStrophePlugins();
148
+        registerListeners();
149
+        Moderator.init();
150
+        var jid = uiCredentials.jid ||
151
+            config.hosts.anonymousdomain ||
152
+            config.hosts.domain ||
153
+            window.location.hostname;
154
+        connect(jid, null, uiCredentials);
155
+    },
156
+    promptLogin: function () {
157
+        UI.showLoginPopup(connect);
158
+    },
159
+    joinRooom: function(roomName, useNicks, nick)
160
+    {
161
+        var roomjid;
162
+        roomjid = roomName;
163
+
164
+        if (useNicks) {
165
+            if (nick) {
166
+                roomjid += '/' + nick;
167
+            } else {
168
+                roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
169
+            }
170
+        } else {
171
+
172
+            var tmpJid = Strophe.getNodeFromJid(connection.jid);
173
+
174
+            if(!authenticatedUser)
175
+                tmpJid = tmpJid.substr(0, 8);
176
+
177
+            roomjid += '/' + tmpJid;
178
+        }
179
+        connection.emuc.doJoin(roomjid);
180
+    },
181
+    myJid: function () {
182
+        if(!connection)
183
+            return null;
184
+        return connection.emuc.myroomjid;
185
+    },
186
+    myResource: function () {
187
+        if(!connection || ! connection.emuc.myroomjid)
188
+            return null;
189
+        return Strophe.getResourceFromJid(connection.emuc.myroomjid);
190
+    },
191
+    disposeConference: function (onUnload) {
192
+        eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload);
193
+        var handler = activecall;
194
+        if (handler && handler.peerconnection) {
195
+            // FIXME: probably removing streams is not required and close() should
196
+            // be enough
197
+            if (RTC.localAudio) {
198
+                handler.peerconnection.removeStream(RTC.localAudio.getOriginalStream(), onUnload);
199
+            }
200
+            if (RTC.localVideo) {
201
+                handler.peerconnection.removeStream(RTC.localVideo.getOriginalStream(), onUnload);
202
+            }
203
+            handler.peerconnection.close();
204
+        }
205
+        activecall = null;
206
+        if(!onUnload)
207
+        {
208
+            this.sessionTerminated = true;
209
+            connection.emuc.doLeave();
210
+        }
211
+    },
212
+    addListener: function(type, listener)
213
+    {
214
+        eventEmitter.on(type, listener);
215
+    },
216
+    removeListener: function (type, listener) {
217
+        eventEmitter.removeListener(type, listener);
218
+    },
219
+    allocateConferenceFocus: function(roomName, callback) {
220
+        Moderator.allocateConferenceFocus(roomName, callback);
221
+    },
222
+    isModerator: function () {
223
+        return Moderator.isModerator();
224
+    },
225
+    isSipGatewayEnabled: function () {
226
+        return Moderator.isSipGatewayEnabled();
227
+    },
228
+    isExternalAuthEnabled: function () {
229
+        return Moderator.isExternalAuthEnabled();
230
+    },
231
+    switchStreams: function (stream, oldStream, callback) {
232
+        if (activecall) {
233
+            // FIXME: will block switchInProgress on true value in case of exception
234
+            activecall.switchStreams(stream, oldStream, callback);
235
+        } else {
236
+            // We are done immediately
237
+            console.error("No conference handler");
238
+            UI.messageHandler.showError('Error',
239
+                'Unable to switch video stream.');
240
+            callback();
241
+        }
242
+    },
243
+    setVideoMute: function (mute, callback, options) {
244
+       if(activecall && connection && RTC.localVideo)
245
+       {
246
+           activecall.setVideoMute(mute, callback, options);
247
+       }
248
+    },
249
+    setAudioMute: function (mute, callback) {
250
+        if (!(connection && RTC.localAudio)) {
251
+            return false;
252
+        }
253
+
254
+
255
+        if (this.forceMuted && !mute) {
256
+            console.info("Asking focus for unmute");
257
+            connection.moderate.setMute(connection.emuc.myroomjid, mute);
258
+            // FIXME: wait for result before resetting muted status
259
+            this.forceMuted = false;
260
+        }
261
+
262
+        if (mute == RTC.localAudio.isMuted()) {
263
+            // Nothing to do
264
+            return true;
265
+        }
266
+
267
+        // It is not clear what is the right way to handle multiple tracks.
268
+        // So at least make sure that they are all muted or all unmuted and
269
+        // that we send presence just once.
270
+        RTC.localAudio.mute();
271
+        // isMuted is the opposite of audioEnabled
272
+        connection.emuc.addAudioInfoToPresence(mute);
273
+        connection.emuc.sendPresence();
274
+        callback();
275
+        return true;
276
+    },
277
+    // Really mute video, i.e. dont even send black frames
278
+    muteVideo: function (pc, unmute) {
279
+        // FIXME: this probably needs another of those lovely state safeguards...
280
+        // which checks for iceconn == connected and sigstate == stable
281
+        pc.setRemoteDescription(pc.remoteDescription,
282
+            function () {
283
+                pc.createAnswer(
284
+                    function (answer) {
285
+                        var sdp = new SDP(answer.sdp);
286
+                        if (sdp.media.length > 1) {
287
+                            if (unmute)
288
+                                sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
289
+                            else
290
+                                sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
291
+                            sdp.raw = sdp.session + sdp.media.join('');
292
+                            answer.sdp = sdp.raw;
293
+                        }
294
+                        pc.setLocalDescription(answer,
295
+                            function () {
296
+                                console.log('mute SLD ok');
297
+                            },
298
+                            function (error) {
299
+                                console.log('mute SLD error');
300
+                                UI.messageHandler.showError('Error',
301
+                                        'Oops! Something went wrong and we failed to ' +
302
+                                        'mute! (SLD Failure)');
303
+                            }
304
+                        );
305
+                    },
306
+                    function (error) {
307
+                        console.log(error);
308
+                        UI.messageHandler.showError();
309
+                    }
310
+                );
311
+            },
312
+            function (error) {
313
+                console.log('muteVideo SRD error');
314
+                UI.messageHandler.showError('Error',
315
+                        'Oops! Something went wrong and we failed to stop video!' +
316
+                        '(SRD Failure)');
317
+
318
+            }
319
+        );
320
+    },
321
+    toggleRecording: function (tokenEmptyCallback,
322
+                               startingCallback, startedCallback) {
323
+        Recording.toggleRecording(tokenEmptyCallback,
324
+            startingCallback, startedCallback);
325
+    },
326
+    addToPresence: function (name, value, dontSend) {
327
+        switch (name)
328
+        {
329
+            case "displayName":
330
+                connection.emuc.addDisplayNameToPresence(value);
331
+                break;
332
+            case "etherpad":
333
+                connection.emuc.addEtherpadToPresence(value);
334
+                break;
335
+            case "prezi":
336
+                connection.emuc.addPreziToPresence(value, 0);
337
+                break;
338
+            case "preziSlide":
339
+                connection.emuc.addCurrentSlideToPresence(value);
340
+                break;
341
+            case "connectionQuality":
342
+                connection.emuc.addConnectionInfoToPresence(value);
343
+                break;
344
+            case "email":
345
+                connection.emuc.addEmailToPresence(value);
346
+            default :
347
+                console.log("Unknown tag for presence.");
348
+                return;
349
+        }
350
+        if(!dontSend)
351
+            connection.emuc.sendPresence();
352
+    },
353
+    sendLogs: function (data) {
354
+        if(!focusMucJid)
355
+            return;
356
+
357
+        var deflate = true;
358
+
359
+        var content = JSON.stringify(dataYes);
360
+        if (deflate) {
361
+            content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
362
+        }
363
+        content = Base64.encode(content);
364
+        // XEP-0337-ish
365
+        var message = $msg({to: focusMucJid, type: 'normal'});
366
+        message.c('log', { xmlns: 'urn:xmpp:eventlog',
367
+            id: 'PeerConnectionStats'});
368
+        message.c('message').t(content).up();
369
+        if (deflate) {
370
+            message.c('tag', {name: "deflated", value: "true"}).up();
371
+        }
372
+        message.up();
373
+
374
+        connection.send(message);
375
+    },
376
+    populateData: function () {
377
+        var data = {};
378
+        if (connection.jingle) {
379
+            data = connection.jingle.populateData();
380
+        }
381
+        return data;
382
+    },
383
+    getLogger: function () {
384
+        if(connection.logger)
385
+            return connection.logger.log;
386
+        return null;
387
+    },
388
+    getPrezi: function () {
389
+        return connection.emuc.getPrezi(this.myJid());
390
+    },
391
+    removePreziFromPresence: function () {
392
+        connection.emuc.removePreziFromPresence();
393
+        connection.emuc.sendPresence();
394
+    },
395
+    sendChatMessage: function (message, nickname) {
396
+        connection.emuc.sendMessage(message, nickname);
397
+    },
398
+    setSubject: function (topic) {
399
+        connection.emuc.setSubject(topic);
400
+    },
401
+    lockRoom: function (key, onSuccess, onError, onNotSupported) {
402
+        connection.emuc.lockRoom(key, onSuccess, onError, onNotSupported);
403
+    },
404
+    dial: function (to, from, roomName,roomPass) {
405
+        connection.rayo.dial(to, from, roomName,roomPass);
406
+    },
407
+    setMute: function (jid, mute) {
408
+        connection.moderate.setMute(jid, mute);
409
+    },
410
+    eject: function (jid) {
411
+        connection.moderate.eject(jid);
412
+    },
413
+    findJidFromResource: function (resource) {
414
+        connection.emuc.findJidFromResource(resource);
415
+    },
416
+    getMembers: function () {
417
+        return connection.emuc.members;
418
+    }
419
+
420
+};
421
+
422
+module.exports = XMPP;

+ 0
- 548
muc.js View File

1
-/* jshint -W117 */
2
-/* a simple MUC connection plugin
3
- * can only handle a single MUC room
4
- */
5
-Strophe.addConnectionPlugin('emuc', {
6
-    connection: null,
7
-    roomjid: null,
8
-    myroomjid: null,
9
-    members: {},
10
-    list_members: [], // so we can elect a new focus
11
-    presMap: {},
12
-    preziMap: {},
13
-    joined: false,
14
-    isOwner: false,
15
-    role: null,
16
-    init: function (conn) {
17
-        this.connection = conn;
18
-    },
19
-    initPresenceMap: function (myroomjid) {
20
-        this.presMap['to'] = myroomjid;
21
-        this.presMap['xns'] = 'http://jabber.org/protocol/muc';
22
-    },
23
-    doJoin: function (jid, password) {
24
-        this.myroomjid = jid;
25
-
26
-        console.info("Joined MUC as " + this.myroomjid);
27
-
28
-        this.initPresenceMap(this.myroomjid);
29
-
30
-        if (!this.roomjid) {
31
-            this.roomjid = Strophe.getBareJidFromJid(jid);
32
-            // add handlers (just once)
33
-            this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, this.roomjid, {matchBare: true});
34
-            this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null, this.roomjid, {matchBare: true});
35
-            this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null, this.roomjid, {matchBare: true});
36
-            this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null, this.roomjid, {matchBare: true});
37
-        }
38
-        if (password !== undefined) {
39
-            this.presMap['password'] = password;
40
-        }
41
-        this.sendPresence();
42
-    },
43
-    doLeave: function() {
44
-        console.log("do leave", this.myroomjid);
45
-        var pres = $pres({to: this.myroomjid, type: 'unavailable' });
46
-        this.presMap.length = 0;
47
-        this.connection.send(pres);
48
-    },
49
-    createNonAnonymousRoom: function() {
50
-        // http://xmpp.org/extensions/xep-0045.html#createroom-reserved
51
-
52
-        var getForm = $iq({type: 'get', to: this.roomjid})
53
-            .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'})
54
-            .c('x', {xmlns: 'jabber:x:data', type: 'submit'});
55
-
56
-        this.connection.sendIQ(getForm, function (form){
57
-
58
-            if (!$(form).find(
59
-                '>query>x[xmlns="jabber:x:data"]' +
60
-                '>field[var="muc#roomconfig_whois"]').length) {
61
-
62
-                console.error('non-anonymous rooms not supported');
63
-                return;
64
-            }
65
-
66
-            var formSubmit = $iq({to: this.roomjid, type: 'set'})
67
-                .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
68
-
69
-            formSubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
70
-
71
-            formSubmit.c('field', {'var': 'FORM_TYPE'})
72
-                .c('value')
73
-                .t('http://jabber.org/protocol/muc#roomconfig').up().up();
74
-
75
-            formSubmit.c('field', {'var': 'muc#roomconfig_whois'})
76
-                .c('value').t('anyone').up().up();
77
-
78
-            this.connection.sendIQ(formSubmit);
79
-
80
-        }, function (error){
81
-            console.error("Error getting room configuration form");
82
-        });
83
-    },
84
-    onPresence: function (pres) {
85
-        var from = pres.getAttribute('from');
86
-
87
-        // What is this for? A workaround for something?
88
-        if (pres.getAttribute('type')) {
89
-            return true;
90
-        }
91
-
92
-        // Parse etherpad tag.
93
-        var etherpad = $(pres).find('>etherpad');
94
-        if (etherpad.length) {
95
-            if (config.etherpad_base && !Moderator.isModerator()) {
96
-                UI.initEtherpad(etherpad.text());
97
-            }
98
-        }
99
-
100
-        // Parse prezi tag.
101
-        var presentation = $(pres).find('>prezi');
102
-        if (presentation.length)
103
-        {
104
-            var url = presentation.attr('url');
105
-            var current = presentation.find('>current').text();
106
-
107
-            console.log('presentation info received from', from, url);
108
-
109
-            if (this.preziMap[from] == null) {
110
-                this.preziMap[from] = url;
111
-
112
-                $(document).trigger('presentationadded.muc', [from, url, current]);
113
-            }
114
-            else {
115
-                $(document).trigger('gotoslide.muc', [from, url, current]);
116
-            }
117
-        }
118
-        else if (this.preziMap[from] != null) {
119
-            var url = this.preziMap[from];
120
-            delete this.preziMap[from];
121
-            $(document).trigger('presentationremoved.muc', [from, url]);
122
-        }
123
-
124
-        // Parse audio info tag.
125
-        var audioMuted = $(pres).find('>audiomuted');
126
-        if (audioMuted.length) {
127
-            $(document).trigger('audiomuted.muc', [from, audioMuted.text()]);
128
-        }
129
-
130
-        // Parse video info tag.
131
-        var videoMuted = $(pres).find('>videomuted');
132
-        if (videoMuted.length) {
133
-            $(document).trigger('videomuted.muc', [from, videoMuted.text()]);
134
-        }
135
-
136
-        var stats = $(pres).find('>stats');
137
-        if(stats.length)
138
-        {
139
-            var statsObj = {};
140
-            Strophe.forEachChild(stats[0], "stat", function (el) {
141
-                statsObj[el.getAttribute("name")] = el.getAttribute("value");
142
-            });
143
-            connectionquality.updateRemoteStats(from, statsObj);
144
-        }
145
-
146
-        // Parse status.
147
-        if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) {
148
-            this.isOwner = true;
149
-            this.createNonAnonymousRoom();
150
-        }
151
-
152
-        // Parse roles.
153
-        var member = {};
154
-        member.show = $(pres).find('>show').text();
155
-        member.status = $(pres).find('>status').text();
156
-        var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item');
157
-        member.affiliation = tmp.attr('affiliation');
158
-        member.role = tmp.attr('role');
159
-
160
-        // Focus recognition
161
-        member.jid = tmp.attr('jid');
162
-        member.isFocus = false;
163
-        if (member.jid
164
-            && member.jid.indexOf(Moderator.getFocusUserJid() + "/") == 0) {
165
-            member.isFocus = true;
166
-        }
167
-
168
-        var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]');
169
-        member.displayName = (nicktag.length > 0 ? nicktag.html() : null);
170
-
171
-        if (from == this.myroomjid) {
172
-            if (member.affiliation == 'owner') this.isOwner = true;
173
-            if (this.role !== member.role) {
174
-                this.role = member.role;
175
-                if(Moderator.onLocalRoleChange)
176
-                    Moderator.onLocalRoleChange(from, member, pres);
177
-                UI.onLocalRoleChange(from, member, pres);
178
-            }
179
-            if (!this.joined) {
180
-                this.joined = true;
181
-                $(document).trigger('joined.muc', [from, member]);
182
-                UI.onMucJoined(from, member);
183
-                this.list_members.push(from);
184
-            }
185
-        } else if (this.members[from] === undefined) {
186
-            // new participant
187
-            this.members[from] = member;
188
-            this.list_members.push(from);
189
-            console.log('entered', from, member);
190
-            if (member.isFocus)
191
-            {
192
-                focusMucJid = from;
193
-                console.info("Ignore focus: " + from +", real JID: " + member.jid);
194
-            }
195
-            else {
196
-                var id = $(pres).find('>userID').text();
197
-                var email = $(pres).find('>email');
198
-                if (email.length > 0) {
199
-                    id = email.text();
200
-                }
201
-                UI.onMucEntered(from, id, member.displayName);
202
-                API.triggerEvent("participantJoined",{jid: from});
203
-            }
204
-        } else {
205
-            // Presence update for existing participant
206
-            // Watch role change:
207
-            if (this.members[from].role != member.role) {
208
-                this.members[from].role = member.role;
209
-                UI.onMucRoleChanged(member.role, member.displayName);
210
-            }
211
-        }
212
-
213
-        // Always trigger presence to update bindings
214
-        $(document).trigger('presence.muc', [from, member, pres]);
215
-
216
-        // Trigger status message update
217
-        if (member.status) {
218
-            UI.onMucPresenceStatus(from, member);
219
-        }
220
-
221
-        return true;
222
-    },
223
-    onPresenceUnavailable: function (pres) {
224
-        var from = pres.getAttribute('from');
225
-        // Status code 110 indicates that this notification is "self-presence".
226
-        if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) {
227
-            delete this.members[from];
228
-            this.list_members.splice(this.list_members.indexOf(from), 1);
229
-            this.onParticipantLeft(from);
230
-        }
231
-        // If the status code is 110 this means we're leaving and we would like
232
-        // to remove everyone else from our view, so we trigger the event.
233
-        else if (this.list_members.length > 1) {
234
-            for (var i = 0; i < this.list_members.length; i++) {
235
-                var member = this.list_members[i];
236
-                delete this.members[i];
237
-                this.list_members.splice(i, 1);
238
-                this.onParticipantLeft(member);
239
-            }
240
-        }
241
-        if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) {
242
-            $(document).trigger('kicked.muc', [from]);
243
-        }
244
-        return true;
245
-    },
246
-    onPresenceError: function (pres) {
247
-        var from = pres.getAttribute('from');
248
-        if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
249
-            console.log('on password required', from);
250
-
251
-            UI.onPasswordReqiured(function (value) {
252
-                connection.emuc.doJoin(from, value);
253
-            })
254
-        } else if ($(pres).find(
255
-                '>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
256
-            var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));
257
-            if(toDomain === config.hosts.anonymousdomain) {
258
-                // we are connected with anonymous domain and only non anonymous users can create rooms
259
-                // we must authorize the user
260
-                $(document).trigger('passwordrequired.main');
261
-            } else {
262
-                console.warn('onPresError ', pres);
263
-                UI.messageHandler.openReportDialog(null,
264
-                    'Oops! Something went wrong and we couldn`t connect to the conference.',
265
-                pres);
266
-            }
267
-        } else {
268
-            console.warn('onPresError ', pres);
269
-            UI.messageHandler.openReportDialog(null,
270
-                'Oops! Something went wrong and we couldn`t connect to the conference.',
271
-                pres);
272
-        }
273
-        return true;
274
-    },
275
-    sendMessage: function (body, nickname) {
276
-        var msg = $msg({to: this.roomjid, type: 'groupchat'});
277
-        msg.c('body', body).up();
278
-        if (nickname) {
279
-            msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up();
280
-        }
281
-        this.connection.send(msg);
282
-        API.triggerEvent("outgoingMessage", {"message": body});
283
-    },
284
-    setSubject: function (subject){
285
-        var msg = $msg({to: this.roomjid, type: 'groupchat'});
286
-        msg.c('subject', subject);
287
-        this.connection.send(msg);
288
-        console.log("topic changed to " + subject);
289
-    },
290
-    onMessage: function (msg) {
291
-        // FIXME: this is a hack. but jingle on muc makes nickchanges hard
292
-        var from = msg.getAttribute('from');
293
-        var nick = $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]').text() || Strophe.getResourceFromJid(from);
294
-
295
-        var txt = $(msg).find('>body').text();
296
-        var type = msg.getAttribute("type");
297
-        if(type == "error")
298
-        {
299
-            UI.chatAddError($(msg).find('>text').text(), txt);
300
-            return true;
301
-        }
302
-
303
-        var subject = $(msg).find('>subject');
304
-        if(subject.length)
305
-        {
306
-            var subjectText = subject.text();
307
-            if(subjectText || subjectText == "") {
308
-                UI.chatSetSubject(subjectText);
309
-                console.log("Subject is changed to " + subjectText);
310
-            }
311
-        }
312
-
313
-
314
-        if (txt) {
315
-            console.log('chat', nick, txt);
316
-            UI.updateChatConversation(from, nick, txt);
317
-            if(from != this.myroomjid)
318
-                API.triggerEvent("incomingMessage",
319
-                    {"from": from, "nick": nick, "message": txt});
320
-        }
321
-        return true;
322
-    },
323
-    lockRoom: function (key, onSuccess, onError, onNotSupported) {
324
-        //http://xmpp.org/extensions/xep-0045.html#roomconfig
325
-        var ob = this;
326
-        this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}),
327
-            function (res) {
328
-                if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) {
329
-                    var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
330
-                    formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
331
-                    formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up();
332
-                    formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up();
333
-                    // Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373
334
-                    formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up();
335
-                    // FIXME: is muc#roomconfig_passwordprotectedroom required?
336
-                    this.connection.sendIQ(formsubmit,
337
-                        onSuccess,
338
-                        onError);
339
-                } else {
340
-                    onNotSupported();
341
-                }
342
-            }, onError);
343
-    },
344
-    kick: function (jid) {
345
-        var kickIQ = $iq({to: this.roomjid, type: 'set'})
346
-            .c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'})
347
-            .c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'})
348
-            .c('reason').t('You have been kicked.').up().up().up();
349
-
350
-        this.connection.sendIQ(
351
-                kickIQ,
352
-                function (result) {
353
-                    console.log('Kick participant with jid: ', jid, result);
354
-                },
355
-                function (error) {
356
-                    console.log('Kick participant error: ', error);
357
-                });
358
-    },
359
-    sendPresence: function () {
360
-        var pres = $pres({to: this.presMap['to'] });
361
-        pres.c('x', {xmlns: this.presMap['xns']});
362
-
363
-        if (this.presMap['password']) {
364
-            pres.c('password').t(this.presMap['password']).up();
365
-        }
366
-
367
-        pres.up();
368
-
369
-        // Send XEP-0115 'c' stanza that contains our capabilities info
370
-        if (connection.caps) {
371
-            connection.caps.node = config.clientNode;
372
-            pres.c('c', connection.caps.generateCapsAttrs()).up();
373
-        }
374
-
375
-        pres.c('user-agent', {xmlns: 'http://jitsi.org/jitmeet/user-agent'})
376
-            .t(navigator.userAgent).up();
377
-
378
-        if(this.presMap['bridgeIsDown']) {
379
-            pres.c('bridgeIsDown').up();
380
-        }
381
-
382
-        if(this.presMap['email']) {
383
-            pres.c('email').t(this.presMap['email']).up();
384
-        }
385
-
386
-        if(this.presMap['userId']) {
387
-            pres.c('userId').t(this.presMap['userId']).up();
388
-        }
389
-
390
-        if (this.presMap['displayName']) {
391
-            // XEP-0172
392
-            pres.c('nick', {xmlns: 'http://jabber.org/protocol/nick'})
393
-                .t(this.presMap['displayName']).up();
394
-        }
395
-
396
-        if (this.presMap['audions']) {
397
-            pres.c('audiomuted', {xmlns: this.presMap['audions']})
398
-                .t(this.presMap['audiomuted']).up();
399
-        }
400
-
401
-        if (this.presMap['videons']) {
402
-            pres.c('videomuted', {xmlns: this.presMap['videons']})
403
-                .t(this.presMap['videomuted']).up();
404
-        }
405
-
406
-        if(this.presMap['statsns'])
407
-        {
408
-            var stats = pres.c('stats', {xmlns: this.presMap['statsns']});
409
-            for(var stat in this.presMap["stats"])
410
-                if(this.presMap["stats"][stat] != null)
411
-                    stats.c("stat",{name: stat, value: this.presMap["stats"][stat]}).up();
412
-            pres.up();
413
-        }
414
-
415
-        if (this.presMap['prezins']) {
416
-            pres.c('prezi',
417
-                    {xmlns: this.presMap['prezins'],
418
-                    'url': this.presMap['preziurl']})
419
-                    .c('current').t(this.presMap['prezicurrent']).up().up();
420
-        }
421
-
422
-        if (this.presMap['etherpadns']) {
423
-            pres.c('etherpad', {xmlns: this.presMap['etherpadns']})
424
-                .t(this.presMap['etherpadname']).up();
425
-        }
426
-
427
-        if (this.presMap['medians'])
428
-        {
429
-            pres.c('media', {xmlns: this.presMap['medians']});
430
-            var sourceNumber = 0;
431
-            Object.keys(this.presMap).forEach(function (key) {
432
-                if (key.indexOf('source') >= 0) {
433
-                     sourceNumber++;
434
-                }
435
-            });
436
-            if (sourceNumber > 0)
437
-                for (var i = 1; i <= sourceNumber/3; i ++) {
438
-                    pres.c('source',
439
-                           {type: this.presMap['source' + i + '_type'],
440
-                           ssrc: this.presMap['source' + i + '_ssrc'],
441
-                           direction: this.presMap['source'+ i + '_direction']
442
-                                                    || 'sendrecv' }
443
-                    ).up();
444
-                }
445
-        }
446
-
447
-        pres.up();
448
-//        console.debug(pres.toString());
449
-        connection.send(pres);
450
-    },
451
-    addDisplayNameToPresence: function (displayName) {
452
-        this.presMap['displayName'] = displayName;
453
-    },
454
-    addMediaToPresence: function (sourceNumber, mtype, ssrcs, direction) {
455
-        if (!this.presMap['medians'])
456
-            this.presMap['medians'] = 'http://estos.de/ns/mjs';
457
-
458
-        this.presMap['source' + sourceNumber + '_type'] = mtype;
459
-        this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
460
-        this.presMap['source' + sourceNumber + '_direction'] = direction;
461
-    },
462
-    clearPresenceMedia: function () {
463
-        var self = this;
464
-        Object.keys(this.presMap).forEach( function(key) {
465
-            if(key.indexOf('source') != -1) {
466
-                delete self.presMap[key];
467
-            }
468
-        });
469
-    },
470
-    addPreziToPresence: function (url, currentSlide) {
471
-        this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
472
-        this.presMap['preziurl'] = url;
473
-        this.presMap['prezicurrent'] = currentSlide;
474
-    },
475
-    removePreziFromPresence: function () {
476
-        delete this.presMap['prezins'];
477
-        delete this.presMap['preziurl'];
478
-        delete this.presMap['prezicurrent'];
479
-    },
480
-    addCurrentSlideToPresence: function (currentSlide) {
481
-        this.presMap['prezicurrent'] = currentSlide;
482
-    },
483
-    getPrezi: function (roomjid) {
484
-        return this.preziMap[roomjid];
485
-    },
486
-    addEtherpadToPresence: function(etherpadName) {
487
-        this.presMap['etherpadns'] = 'http://jitsi.org/jitmeet/etherpad';
488
-        this.presMap['etherpadname'] = etherpadName;
489
-    },
490
-    addAudioInfoToPresence: function(isMuted) {
491
-        this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio';
492
-        this.presMap['audiomuted'] = isMuted.toString();
493
-    },
494
-    addVideoInfoToPresence: function(isMuted) {
495
-        this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
496
-        this.presMap['videomuted'] = isMuted.toString();
497
-    },
498
-    addConnectionInfoToPresence: function(stats) {
499
-        this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats';
500
-        this.presMap['stats'] = stats;
501
-    },
502
-    findJidFromResource: function(resourceJid) {
503
-        if(resourceJid && 
504
-            resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
505
-            return connection.emuc.myroomjid;
506
-        }
507
-        var peerJid = null;
508
-        Object.keys(this.members).some(function (jid) {
509
-            peerJid = jid;
510
-            return Strophe.getResourceFromJid(jid) === resourceJid;
511
-        });
512
-        return peerJid;
513
-    },
514
-    addBridgeIsDownToPresence: function() {
515
-        this.presMap['bridgeIsDown'] = true;
516
-    },
517
-    addEmailToPresence: function(email) {
518
-        this.presMap['email'] = email;
519
-    },
520
-    addUserIdToPresence: function(userId) {
521
-        this.presMap['userId'] = userId;
522
-    },
523
-    isModerator: function() {
524
-        return this.role === 'moderator';
525
-    },
526
-    getMemberRole: function(peerJid) {
527
-        if (this.members[peerJid]) {
528
-            return this.members[peerJid].role;
529
-        }
530
-        return null;
531
-    },
532
-    onParticipantLeft: function (jid) {
533
-        UI.onMucLeft(jid);
534
-
535
-        API.triggerEvent("participantLeft",{jid: jid});
536
-
537
-        delete jid2Ssrc[jid];
538
-
539
-        connection.jingle.terminateByJid(jid);
540
-
541
-        if (connection.emuc.getPrezi(jid)) {
542
-            $(document).trigger('presentationremoved.muc',
543
-                [jid, connection.emuc.getPrezi(jid)]);
544
-        }
545
-
546
-        Moderator.onMucLeft(jid);
547
-    }
548
-});

+ 0
- 167
recording.js View File

1
-/* global $, $iq, config, connection, focusMucJid, messageHandler, Moderator,
2
-   Toolbar, Util */
3
-var Recording = (function (my) {
4
-    var recordingToken = null;
5
-    var recordingEnabled;
6
-
7
-    /**
8
-     * Whether to use a jirecon component for recording, or use the videobridge
9
-     * through COLIBRI.
10
-     */
11
-    var useJirecon = (typeof config.hosts.jirecon != "undefined");
12
-
13
-    /**
14
-     * The ID of the jirecon recording session. Jirecon generates it when we
15
-     * initially start recording, and it needs to be used in subsequent requests
16
-     * to jirecon.
17
-     */
18
-    var jireconRid = null;
19
-
20
-    my.setRecordingToken = function (token) {
21
-        recordingToken = token;
22
-    };
23
-
24
-    my.setRecording = function (state, token, callback) {
25
-        if (useJirecon){
26
-            this.setRecordingJirecon(state, token, callback);
27
-        } else {
28
-            this.setRecordingColibri(state, token, callback);
29
-        }
30
-    };
31
-
32
-    my.setRecordingJirecon = function (state, token, callback) {
33
-        if (state == recordingEnabled){
34
-            return;
35
-        }
36
-
37
-        var iq = $iq({to: config.hosts.jirecon, type: 'set'})
38
-            .c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
39
-                action: state ? 'start' : 'stop',
40
-                mucjid: connection.emuc.roomjid});
41
-        if (!state){
42
-            iq.attrs({rid: jireconRid});
43
-        }
44
-
45
-        console.log('Start recording');
46
-
47
-        connection.sendIQ(
48
-            iq,
49
-            function (result) {
50
-                // TODO wait for an IQ with the real status, since this is
51
-                // provisional?
52
-                jireconRid = $(result).find('recording').attr('rid');
53
-                console.log('Recording ' + (state ? 'started' : 'stopped') +
54
-                    '(jirecon)' + result);
55
-                recordingEnabled = state;
56
-                if (!state){
57
-                    jireconRid = null;
58
-                }
59
-
60
-                callback(state);
61
-            },
62
-            function (error) {
63
-                console.log('Failed to start recording, error: ', error);
64
-                callback(recordingEnabled);
65
-            });
66
-    };
67
-
68
-    // Sends a COLIBRI message which enables or disables (according to 'state')
69
-    // the recording on the bridge. Waits for the result IQ and calls 'callback'
70
-    // with the new recording state, according to the IQ.
71
-    my.setRecordingColibri = function (state, token, callback) {
72
-        var elem = $iq({to: focusMucJid, type: 'set'});
73
-        elem.c('conference', {
74
-            xmlns: 'http://jitsi.org/protocol/colibri'
75
-        });
76
-        elem.c('recording', {state: state, token: token});
77
-
78
-        connection.sendIQ(elem,
79
-            function (result) {
80
-                console.log('Set recording "', state, '". Result:', result);
81
-                var recordingElem = $(result).find('>conference>recording');
82
-                var newState = ('true' === recordingElem.attr('state'));
83
-
84
-                recordingEnabled = newState;
85
-                callback(newState);
86
-            },
87
-            function (error) {
88
-                console.warn(error);
89
-                callback(recordingEnabled);
90
-            }
91
-        );
92
-    };
93
-
94
-    my.toggleRecording = function () {
95
-        if (!Moderator.isModerator()) {
96
-            console.log(
97
-                'non-focus, or conference not yet organized:' +
98
-                ' not enabling recording');
99
-            return;
100
-        }
101
-
102
-        // Jirecon does not (currently) support a token.
103
-        if (!recordingToken && !useJirecon)
104
-        {
105
-            UI.messageHandler.openTwoButtonDialog(null,
106
-                    '<h2>Enter recording token</h2>' +
107
-                    '<input id="recordingToken" type="text" ' +
108
-                    'placeholder="token" autofocus>',
109
-                false,
110
-                "Save",
111
-                function (e, v, m, f) {
112
-                    if (v) {
113
-                        var token = document.getElementById('recordingToken');
114
-
115
-                        if (token.value) {
116
-                            my.setRecordingToken(
117
-                                Util.escapeHtml(token.value));
118
-                            my.toggleRecording();
119
-                        }
120
-                    }
121
-                },
122
-                function (event) {
123
-                    document.getElementById('recordingToken').focus();
124
-                },
125
-                function () {}
126
-            );
127
-
128
-            return;
129
-        }
130
-
131
-        var oldState = recordingEnabled;
132
-        UI.setRecordingButtonState(!oldState);
133
-        my.setRecording(!oldState,
134
-            recordingToken,
135
-            function (state) {
136
-                console.log("New recording state: ", state);
137
-                if (state === oldState)
138
-                {
139
-                    // FIXME: new focus:
140
-                    // this will not work when moderator changes
141
-                    // during active session. Then it will assume that
142
-                    // recording status has changed to true, but it might have
143
-                    // been already true(and we only received actual status from
144
-                    // the focus).
145
-                    //
146
-                    // SO we start with status null, so that it is initialized
147
-                    // here and will fail only after second click, so if invalid
148
-                    // token was used we have to press the button twice before
149
-                    // current status will be fetched and token will be reset.
150
-                    //
151
-                    // Reliable way would be to return authentication error.
152
-                    // Or status update when moderator connects.
153
-                    // Or we have to stop recording session when current
154
-                    // moderator leaves the room.
155
-
156
-                    // Failed to change, reset the token because it might
157
-                    // have been wrong
158
-                    my.setRecordingToken(null);
159
-                }
160
-                // Update with returned status
161
-                UI.setRecordingButtonState(state);
162
-            }
163
-        );
164
-    };
165
-
166
-    return my;
167
-}(Recording || {}));

+ 14
- 0
service/xmpp/XMPPEvents.js View File

1
+var XMPPEvents = {
2
+    CONFERENCE_CERATED: "xmpp.conferenceCreated.jingle",
3
+    CALL_TERMINATED: "xmpp.callterminated.jingle",
4
+    CALL_INCOMING: "xmpp.callincoming.jingle",
5
+    DISPOSE_CONFERENCE: "xmpp.dispoce_confernce",
6
+    KICKED: "xmpp.kicked",
7
+    BRIDGE_DOWN: "xmpp.bridge_down",
8
+    USER_ID_CHANGED: "xmpp.user_id_changed",
9
+    CHANGED_STREAMS: "xmpp.changed_streams",
10
+    MUC_JOINED: "xmpp.muc_joined",
11
+    DISPLAY_NAME_CHANGED: "xmpp.display_name_changed",
12
+    REMOTE_STATS: "xmpp.remote_stats"
13
+};
14
+//module.exports = XMPPEvents;

Loading…
Cancel
Save