Przeglądaj źródła

Creates initial version of xmpp module.

j8
hristoterezov 10 lat temu
rodzic
commit
e4e66a03d7
56 zmienionych plików z 8947 dodań i 3386 usunięć
  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 Wyświetl plik

@@ -1,17 +1,8 @@
1 1
 /* jshint -W117 */
2 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 3
 var nickname = null;
11 4
 var focusMucJid = null;
12
-var roomName = null;
13 5
 var ssrc2jid = {};
14
-var bridgeIsDown = false;
15 6
 //TODO: this array must be removed when firefox implement multistream support
16 7
 var notReceivedSSRCs = [];
17 8
 
@@ -27,674 +18,12 @@ var ssrc2videoType = {};
27 18
  * @type {String}
28 19
  */
29 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 22
 function init() {
51 23
 
52
-
53
-    RTC.addStreamListener(maybeDoJoin, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
54 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,60 +35,12 @@ $(document).ready(function () {
706 35
     UI.start();
707 36
     statistics.start();
708 37
     
709
-    Moderator.init();
710
-
711 38
     // Set default desktop sharing method
712 39
     desktopsharing.init();
713 40
 });
714 41
 
715 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 43
     if(API.isEnabled())
739 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 Wyświetl plik

@@ -1,17 +0,0 @@
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 Wyświetl plik

@@ -11,16 +11,10 @@
11 11
     <meta itemprop="image" content="/images/jitsilogo.png"/>
12 12
     <script src="libs/jquery-2.1.1.min.js"></script>
13 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 14
     <script src="libs/strophe/strophe.min.js?v=1"></script>
16 15
     <script src="libs/strophe/strophe.disco.min.js?v=1"></script>
17 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 17
     <script src="libs/jquery-ui.js"></script>
23
-    <script src="libs/rayo.js?v=1"></script>
24 18
     <script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
25 19
     <script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
26 20
     <script src="libs/pako.bundle.js?v=1"></script><!-- zlib deflate -->
@@ -29,22 +23,20 @@
29 23
     <script src="service/RTC/RTCBrowserType.js?v=1"></script>
30 24
     <script src="service/RTC/StreamEventTypes.js?v=2"></script>
31 25
     <script src="service/RTC/MediaStreamTypes.js?v=1"></script>
26
+    <script src="service/xmpp/XMPPEvents.js?v=1"></script>
32 27
     <script src="service/desktopsharing/DesktopSharingEventTypes.js?v=1"></script>
33 28
     <script src="libs/modules/simulcast.bundle.js?v=3"></script>
34 29
     <script src="libs/modules/connectionquality.bundle.js?v=1"></script>
35 30
     <script src="libs/modules/UI.bundle.js?v=5"></script>
36 31
     <script src="libs/modules/statistics.bundle.js?v=1"></script>
37 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 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 36
     <script src="app.js?v=26"></script><!-- application logic -->
42 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 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 40
     <script src="keyboard_shortcut.js?v=4"></script>
49 41
     <link rel="stylesheet" href="css/font.css?v=6"/>
50 42
     <link rel="stylesheet" href="css/toastr.css?v=1">

+ 4
- 4
keyboard_shortcut.js Wyświetl plik

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

+ 4
- 3
libs/modules/API.bundle.js
Plik diff jest za duży
Wyświetl plik


+ 8
- 15
libs/modules/RTC.bundle.js
Plik diff jest za duży
Wyświetl plik


+ 596
- 285
libs/modules/UI.bundle.js
Plik diff jest za duży
Wyświetl plik


+ 3
- 3
libs/modules/connectionquality.bundle.js
Plik diff jest za duży
Wyświetl plik


+ 3
- 2
libs/modules/desktopsharing.bundle.js
Plik diff jest za duży
Wyświetl plik


+ 34
- 72
libs/modules/simulcast.bundle.js
Plik diff jest za duży
Wyświetl plik


+ 13
- 22
libs/modules/statistics.bundle.js
Plik diff jest za duży
Wyświetl plik


+ 5082
- 0
libs/modules/xmpp.bundle.js
Plik diff jest za duży
Wyświetl plik


+ 0
- 103
libs/rayo.js Wyświetl plik

@@ -1,103 +0,0 @@
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 Wyświetl plik

@@ -1,327 +0,0 @@
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 Wyświetl plik

@@ -1,41 +0,0 @@
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 Wyświetl plik

@@ -1,56 +0,0 @@
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 Wyświetl plik

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

+ 2
- 2
modules/RTC/DataChannels.js Wyświetl plik

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

+ 2
- 11
modules/RTC/RTC.js Wyświetl plik

@@ -58,7 +58,7 @@ var RTC = {
58 58
     createRemoteStream: function (data, sid, thessrc) {
59 59
         var remoteStream = new MediaStream(data, sid, thessrc, eventEmitter,
60 60
             this.getBrowserType());
61
-        var jid = data.peerjid || connection.emuc.myroomjid;
61
+        var jid = data.peerjid || xmpp.myJid();
62 62
         if(!this.remoteStreams[jid]) {
63 63
             this.remoteStreams[jid] = {};
64 64
         }
@@ -144,16 +144,7 @@ var RTC = {
144 144
         RTC.localVideo = this.createLocalStream(stream, type, true);
145 145
         // Stop the stream to trigger onended event for old stream
146 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 Wyświetl plik

@@ -17,9 +17,11 @@ var PanelToggler = require("./side_pannels/SidePanelToggler");
17 17
 var RoomNameGenerator = require("./welcome_page/RoomnameGenerator");
18 18
 UI.messageHandler = require("./util/MessageHandler");
19 19
 var messageHandler = UI.messageHandler;
20
+var Authentication  = require("./authentication/Authentication");
21
+var UIUtil = require("./util/UIUtil");
20 22
 
21 23
 //var eventEmitter = new EventEmitter();
22
-
24
+var roomName = null;
23 25
 
24 26
 
25 27
 function setupPrezi()
@@ -39,7 +41,7 @@ function setupChat()
39 41
 }
40 42
 
41 43
 function setupToolbars() {
42
-    Toolbar.init();
44
+    Toolbar.init(UI);
43 45
     Toolbar.setupButtonsFromConfig();
44 46
     BottomToolbar.init();
45 47
 }
@@ -62,6 +64,16 @@ function streamHandler(stream) {
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 77
 function registerListeners() {
66 78
     RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
67 79
 
@@ -70,14 +82,7 @@ function registerListeners() {
70 82
         VideoLayout.onRemoteStreamAdded(stream);
71 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 87
     statistics.addAudioLevelListener(function(jid, audioLevel)
83 88
     {
@@ -104,8 +109,38 @@ function registerListeners() {
104 109
     desktopsharing.addListener(
105 110
         Toolbar.changeDesktopSharingButtonState,
106 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 146
 function bindEvents()
@@ -117,10 +152,6 @@ function bindEvents()
117 152
         function () {
118 153
             VideoLayout.resizeLargeVideoContainer();
119 154
             VideoLayout.positionLarge();
120
-            isFullScreen = document.fullScreen ||
121
-                document.mozFullScreen ||
122
-                document.webkitIsFullScreen;
123
-
124 155
         }
125 156
     );
126 157
 
@@ -255,11 +286,6 @@ UI.start = function () {
255 286
 
256 287
 };
257 288
 
258
-
259
-UI.setUserAvatar = function (jid, id) {
260
-    Avatar.setUserAvatar(jid, id);
261
-};
262
-
263 289
 UI.toggleSmileys = function () {
264 290
     Chat.toggleSmileys();
265 291
 };
@@ -278,7 +304,7 @@ UI.updateChatConversation = function (from, displayName, message) {
278 304
     return Chat.updateChatConversation(from, displayName, message);
279 305
 };
280 306
 
281
-UI.onMucJoined = function (jid, info) {
307
+function onMucJoined(jid, info) {
282 308
     Toolbar.updateRoomUrl(window.location.href);
283 309
     document.getElementById('localNick').appendChild(
284 310
         document.createTextNode(Strophe.getResourceFromJid(jid) + ' (me)')
@@ -293,15 +319,14 @@ UI.onMucJoined = function (jid, info) {
293 319
 
294 320
     // Show authenticate button if needed
295 321
     Toolbar.showAuthenticateButton(
296
-            Moderator.isExternalAuthEnabled() && !Moderator.isModerator());
322
+            xmpp.isExternalAuthEnabled() && !xmpp.isModerator());
297 323
 
298 324
     var displayName = !config.displayJids
299 325
         ? info.displayName : Strophe.getResourceFromJid(jid);
300 326
 
301 327
     if (displayName)
302
-        $(document).trigger('displaynamechanged',
303
-            ['localVideoContainer', displayName + ' (me)']);
304
-};
328
+        onDisplayNameChanged('localVideoContainer', displayName + ' (me)');
329
+}
305 330
 
306 331
 UI.initEtherpad = function (name) {
307 332
     Etherpad.init(name);
@@ -357,23 +382,19 @@ UI.toggleContactList = function () {
357 382
 UI.onLocalRoleChange = function (jid, info, pres) {
358 383
 
359 384
     console.info("My role changed, new role: " + info.role);
360
-    var isModerator = Moderator.isModerator();
385
+    var isModerator = xmpp.isModerator();
361 386
 
362 387
     VideoLayout.showModeratorIndicator();
363 388
     Toolbar.showAuthenticateButton(
364
-            Moderator.isExternalAuthEnabled() && !isModerator);
389
+            xmpp.isExternalAuthEnabled() && !isModerator);
365 390
 
366 391
     if (isModerator) {
367
-        Toolbar.closeAuthenticationWindow();
392
+        Authentication.closeAuthenticationWindow();
368 393
         messageHandler.notify(
369 394
             'Me', 'connected', 'Moderator rights granted !');
370 395
     }
371 396
 };
372 397
 
373
-UI.onDisposeConference = function (unload) {
374
-    Toolbar.showAuthenticateButton(false);
375
-};
376
-
377 398
 UI.onModeratorStatusChanged = function (isModerator) {
378 399
 
379 400
     Toolbar.showSipCallButton(isModerator);
@@ -414,40 +435,11 @@ UI.onPasswordReqiured = function (callback) {
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 445
 UI.setRecordingButtonState = function (state) {
@@ -511,6 +503,8 @@ UI.showLocalAudioIndicator = function (mute) {
511 503
 };
512 504
 
513 505
 UI.generateRoomName = function() {
506
+    if(roomName)
507
+        return roomName;
514 508
     var roomnode = null;
515 509
     var path = window.location.pathname;
516 510
 
@@ -540,6 +534,7 @@ UI.generateRoomName = function() {
540 534
     }
541 535
 
542 536
     roomName = roomnode + '@' + config.hosts.muc;
537
+    return roomName;
543 538
 };
544 539
 
545 540
 
@@ -556,25 +551,146 @@ UI.dockToolbar = function (isDock) {
556 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 605
 function dump(elem, filename) {
560 606
     elem = elem.parentNode;
561 607
     elem.download = filename || 'meetlog.json';
562 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 610
     var metadata = {};
568 611
     metadata.time = new Date();
569 612
     metadata.url = window.location.href;
570 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 618
     data.metadata = metadata;
575 619
     elem.href += encodeURIComponent(JSON.stringify(data, null, '  '));
576 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 695
 module.exports = UI;
580 696
 

+ 4
- 4
modules/UI/audio_levels/AudioLevels.js Wyświetl plik

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

+ 84
- 0
modules/UI/authentication/Authentication.js Wyświetl plik

@@ -0,0 +1,84 @@
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 Wyświetl plik

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

+ 2
- 3
modules/UI/etherpad/Etherpad.js Wyświetl plik

@@ -1,4 +1,4 @@
1
-/* global $, config, connection, dockToolbar, Moderator,
1
+/* global $, config, dockToolbar,
2 2
    setLargeVideoVisible, Util */
3 3
 
4 4
 var VideoLayout = require("../videolayout/VideoLayout");
@@ -30,8 +30,7 @@ function resize() {
30 30
  * Shares the Etherpad name with other participants.
31 31
  */
32 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 Wyświetl plik

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

+ 3
- 2
modules/UI/side_pannels/SidePanelToggler.js Wyświetl plik

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

+ 4
- 5
modules/UI/side_pannels/chat/Chat.js Wyświetl plik

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

+ 1
- 1
modules/UI/side_pannels/chat/Commands.js Wyświetl plik

@@ -32,7 +32,7 @@ function getCommand(message)
32 32
 function processTopic(commandArguments)
33 33
 {
34 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 Wyświetl plik

@@ -46,23 +46,6 @@ function createDisplayNameParagraph(displayName) {
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 49
 function stopGlowing(glower) {
67 50
     window.clearInterval(notificationInterval);
68 51
     notificationInterval = false;
@@ -127,7 +110,7 @@ var ContactList = {
127 110
 
128 111
         var clElement = contactlist.get(0);
129 112
 
130
-        if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid)
113
+        if (resourceJid === xmpp.myResource()
131 114
             && $('#contactlist>ul .title')[0].nextSibling.nextSibling) {
132 115
             clElement.insertBefore(newContact,
133 116
                 $('#contactlist>ul .title')[0].nextSibling.nextSibling);
@@ -182,6 +165,18 @@ var ContactList = {
182 165
         } else {
183 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 Wyświetl plik

@@ -10,16 +10,15 @@ var SettingsMenu = {
10 10
 
11 11
         if(newDisplayName) {
12 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 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 24
     isVisible: function() {
@@ -29,14 +28,15 @@ var SettingsMenu = {
29 28
     setDisplayName: function(newDisplayName) {
30 29
         var displayName = Settings.setDisplayName(newDisplayName);
31 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 42
 module.exports = SettingsMenu;

+ 47
- 45
modules/UI/toolbars/Toolbar.js Wyświetl plik

@@ -1,22 +1,24 @@
1
-/* global $, buttonClick, config, lockRoom,  Moderator, roomName,
2
-   setSharedKey, sharedKey, Util */
1
+/* global $, buttonClick, config, lockRoom,
2
+   setSharedKey, Util */
3 3
 var messageHandler = require("../util/MessageHandler");
4 4
 var BottomToolbar = require("./BottomToolbar");
5 5
 var Prezi = require("../prezi/Prezi");
6 6
 var Etherpad = require("../etherpad/Etherpad");
7 7
 var PanelToggler = require("../side_pannels/SidePanelToggler");
8
+var Authentication = require("../authentication/Authentication");
9
+var UIUtil = require("../util/UIUtil");
8 10
 
9 11
 var roomUrl = null;
10 12
 var sharedKey = '';
11
-var authenticationWindow = null;
13
+var UI = null;
12 14
 
13 15
 var buttonHandlers =
14 16
 {
15 17
     "toolbar_button_mute": function () {
16
-        return toggleAudio();
18
+        return UI.toggleAudio();
17 19
     },
18 20
     "toolbar_button_camera": function () {
19
-        return toggleVideo();
21
+        return UI.toggleVideo();
20 22
     },
21 23
     "toolbar_button_authentication": function () {
22 24
         return Toolbar.authenticateClicked();
@@ -44,7 +46,7 @@ var buttonHandlers =
44 46
     },
45 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 50
         return Toolbar.toggleFullScreen();
49 51
     },
50 52
     "toolbar_button_sip": function () {
@@ -59,9 +61,7 @@ var buttonHandlers =
59 61
 };
60 62
 
61 63
 function hangup() {
62
-    disposeConference();
63
-    sessionTerminated = true;
64
-    connection.emuc.doLeave();
64
+    xmpp.disposeConference();
65 65
     if(config.enableWelcomePage)
66 66
     {
67 67
         setTimeout(function()
@@ -90,7 +90,29 @@ function hangup() {
90 90
  */
91 91
 
92 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,7 +123,7 @@ function lockRoom(lock) {
101 123
     if (lock)
102 124
         currentSharedKey = sharedKey;
103 125
 
104
-    connection.emuc.lockRoom(currentSharedKey, function (res) {
126
+    xmpp.lockRoom(currentSharedKey, function (res) {
105 127
         // password is required
106 128
         if (sharedKey)
107 129
         {
@@ -183,9 +205,8 @@ function callSipButtonClicked()
183 205
             if (v) {
184 206
                 var numberInput = document.getElementById('sipNumber');
185 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,9 +218,10 @@ function callSipButtonClicked()
197 218
 
198 219
 var Toolbar = (function (my) {
199 220
 
200
-    my.init = function () {
221
+    my.init = function (ui) {
201 222
         for(var k in buttonHandlers)
202 223
             $("#" + k).click(buttonHandlers[k]);
224
+        UI = ui;
203 225
     }
204 226
 
205 227
     /**
@@ -210,35 +232,15 @@ var Toolbar = (function (my) {
210 232
         sharedKey = sKey;
211 233
     };
212 234
 
213
-    my.closeAuthenticationWindow = function () {
214
-        if (authenticationWindow) {
215
-            authenticationWindow.close();
216
-            authenticationWindow = null;
217
-        }
218
-    }
219
-
220 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 237
         // Get authentication URL
227
-        Moderator.getAuthUrl(function (url) {
238
+        xmpp.getAuthUrl(UI.getRoomName(), function (url) {
228 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 244
             if (!authenticationWindow) {
243 245
                 Toolbar.showAuthenticateButton(true);
244 246
                 messageHandler.openMessageDialog(
@@ -279,7 +281,7 @@ var Toolbar = (function (my) {
279 281
      */
280 282
     my.openLockDialog = function () {
281 283
         // Only the focus is able to set a shared key.
282
-        if (!Moderator.isModerator()) {
284
+        if (!xmpp.isModerator()) {
283 285
             if (sharedKey) {
284 286
                 messageHandler.openMessageDialog(null,
285 287
                         "This conversation is currently protected by" +
@@ -436,14 +438,14 @@ var Toolbar = (function (my) {
436 438
      */
437 439
     my.unlockLockButton = function () {
438 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 444
      * Updates the lock button state to locked.
443 445
      */
444 446
     my.lockLockButton = function () {
445 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,7 +488,7 @@ var Toolbar = (function (my) {
486 488
 
487 489
     // Shows or hides SIP calls button
488 490
     my.showSipCallButton = function (show) {
489
-        if (Moderator.isSipGatewayEnabled() && show) {
491
+        if (xmpp.isSipGatewayEnabled() && show) {
490 492
             $('#sipCallButton').css({display: "inline"});
491 493
         } else {
492 494
             $('#sipCallButton').css({display: "none"});

+ 1
- 1
modules/UI/toolbars/ToolbarToggler.js Wyświetl plik

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

+ 7
- 0
modules/UI/util/UIUtil.js Wyświetl plik

@@ -11,6 +11,13 @@ module.exports = {
11 11
             = PanelToggler.isVisible() ? PanelToggler.getPanelSize()[0] : 0;
12 12
 
13 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 Wyświetl plik

@@ -16,8 +16,99 @@ var largeVideoState = {
16 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 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 113
  * Returns an array of the video horizontal and vertical indents,
23 114
  * so that if fits its parent.
@@ -194,7 +285,7 @@ function getParticipantContainer(resourceJid)
194 285
     if (!resourceJid)
195 286
         return null;
196 287
 
197
-    if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid))
288
+    if (resourceJid === xmpp.myResource())
198 289
         return $("#localVideoContainer");
199 290
     else
200 291
         return $("#participant_" + resourceJid);
@@ -270,7 +361,8 @@ function addRemoteVideoMenu(jid, parentElement) {
270 361
             event.preventDefault();
271 362
         }
272 363
         var isMute = mutedAudios[jid] == true;
273
-        connection.moderate.setMute(jid, !isMute);
364
+        xmpp.setMute(jid, !isMute);
365
+
274 366
         popupmenuElement.setAttribute('style', 'display:none;');
275 367
 
276 368
         if (isMute) {
@@ -292,7 +384,7 @@ function addRemoteVideoMenu(jid, parentElement) {
292 384
     var ejectLinkItem = document.createElement('a');
293 385
     ejectLinkItem.innerHTML = ejectIndicator + ' Kick out';
294 386
     ejectLinkItem.onclick = function(){
295
-        connection.moderate.eject(jid);
387
+        xmpp.eject(jid);
296 388
         popupmenuElement.setAttribute('style', 'display:none;');
297 389
     };
298 390
 
@@ -400,6 +492,43 @@ function createModeratorIndicatorElement(parentElement) {
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 532
 var VideoLayout = (function (my) {
404 533
     my.connectionIndicators = {};
405 534
 
@@ -407,6 +536,16 @@ var VideoLayout = (function (my) {
407 536
     my.getVideoSize = getCameraVideoSize;
408 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 549
     my.isInLastN = function(resource) {
411 550
         return lastNCount < 0 // lastN is disabled, return true
412 551
             || (lastNCount > 0 && lastNEndpointsCache.length == 0) // lastNEndpoints cache not built yet, return true
@@ -422,7 +561,10 @@ var VideoLayout = (function (my) {
422 561
         document.getElementById('localAudio').autoplay = true;
423 562
         document.getElementById('localAudio').volume = 0;
424 563
         if (preMuted) {
425
-            setAudioMuted(true);
564
+            if(!UI.setAudioMuted(true))
565
+            {
566
+                preMuted = mute;
567
+            }
426 568
             preMuted = false;
427 569
         }
428 570
     };
@@ -459,14 +601,14 @@ var VideoLayout = (function (my) {
459 601
             VideoLayout.handleVideoThumbClicked(
460 602
                 RTC.getVideoSrc(localVideo),
461 603
                 false,
462
-                Strophe.getResourceFromJid(connection.emuc.myroomjid));
604
+                xmpp.myResource());
463 605
         });
464 606
         $('#localVideoContainer').click(function (event) {
465 607
             event.stopPropagation();
466 608
             VideoLayout.handleVideoThumbClicked(
467 609
                 RTC.getVideoSrc(localVideo),
468 610
                 false,
469
-                Strophe.getResourceFromJid(connection.emuc.myroomjid));
611
+                xmpp.myResource());
470 612
         });
471 613
 
472 614
         // Add hover handler
@@ -496,11 +638,8 @@ var VideoLayout = (function (my) {
496 638
 
497 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 643
         VideoLayout.updateLargeVideo(localVideoSrc, 0,
505 644
             myResourceJid);
506 645
 
@@ -539,7 +678,7 @@ var VideoLayout = (function (my) {
539 678
                 {
540 679
                     if(container.id == "localVideoWrapper")
541 680
                     {
542
-                        jid = Strophe.getResourceFromJid(connection.emuc.myroomjid);
681
+                        jid = xmpp.myResource();
543 682
                     }
544 683
                     else
545 684
                     {
@@ -617,9 +756,9 @@ var VideoLayout = (function (my) {
617 756
             largeVideoState.isVisible = $('#largeVideo').is(':visible');
618 757
             largeVideoState.isDesktop = isVideoSrcDesktop(resourceJid);
619 758
             if(jid2Ssrc[largeVideoState.userResourceJid] ||
620
-                (connection && connection.emuc.myroomjid &&
759
+                (xmpp.myResource() &&
621 760
                     largeVideoState.userResourceJid ===
622
-                    Strophe.getResourceFromJid(connection.emuc.myroomjid))) {
761
+                    xmpp.myResource())) {
623 762
                 largeVideoState.oldResourceJid = largeVideoState.userResourceJid;
624 763
             } else {
625 764
                 largeVideoState.oldResourceJid = null;
@@ -643,7 +782,7 @@ var VideoLayout = (function (my) {
643 782
                 var doUpdate = function () {
644 783
 
645 784
                     Avatar.updateActiveSpeakerAvatarSrc(
646
-                        connection.emuc.findJidFromResource(
785
+                        xmpp.findJidFromResource(
647 786
                             largeVideoState.userResourceJid));
648 787
 
649 788
                     if (!userChanged && largeVideoState.preload &&
@@ -723,7 +862,7 @@ var VideoLayout = (function (my) {
723 862
 
724 863
                     if(userChanged) {
725 864
                         Avatar.showUserAvatar(
726
-                            connection.emuc.findJidFromResource(
865
+                            xmpp.findJidFromResource(
727 866
                                 largeVideoState.oldResourceJid));
728 867
                     }
729 868
 
@@ -738,7 +877,7 @@ var VideoLayout = (function (my) {
738 877
             }
739 878
         } else {
740 879
             Avatar.showUserAvatar(
741
-                connection.emuc.findJidFromResource(
880
+                xmpp.findJidFromResource(
742 881
                     largeVideoState.userResourceJid));
743 882
         }
744 883
 
@@ -877,7 +1016,7 @@ var VideoLayout = (function (my) {
877 1016
                 focusedVideoInfo = null;
878 1017
                 if(focusResourceJid) {
879 1018
                     Avatar.showUserAvatar(
880
-                        connection.emuc.findJidFromResource(focusResourceJid));
1019
+                        xmpp.findJidFromResource(focusResourceJid));
881 1020
                 }
882 1021
             }
883 1022
         }
@@ -949,7 +1088,7 @@ var VideoLayout = (function (my) {
949 1088
 
950 1089
         // If the peerJid is null then this video span couldn't be directly
951 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 1092
             addRemoteVideoMenu(peerJid, container);
954 1093
 
955 1094
         remotes.appendChild(container);
@@ -1134,13 +1273,13 @@ var VideoLayout = (function (my) {
1134 1273
             if (state == 'show')
1135 1274
             {
1136 1275
                 // peerContainer.css('-webkit-filter', '');
1137
-                var jid = connection.emuc.findJidFromResource(resourceJid);
1276
+                var jid = xmpp.findJidFromResource(resourceJid);
1138 1277
                 Avatar.showUserAvatar(jid, false);
1139 1278
             }
1140 1279
             else // if (state == 'avatar')
1141 1280
             {
1142 1281
                 // peerContainer.css('-webkit-filter', 'grayscale(100%)');
1143
-                var jid = connection.emuc.findJidFromResource(resourceJid);
1282
+                var jid = xmpp.findJidFromResource(resourceJid);
1144 1283
                 Avatar.showUserAvatar(jid, true);
1145 1284
             }
1146 1285
         }
@@ -1166,8 +1305,7 @@ var VideoLayout = (function (my) {
1166 1305
         if (name && nickname !== name) {
1167 1306
             nickname = name;
1168 1307
             window.localStorage.displayname = nickname;
1169
-            connection.emuc.addDisplayNameToPresence(nickname);
1170
-            connection.emuc.sendPresence();
1308
+            xmpp.addToPresence("displayName", nickname);
1171 1309
 
1172 1310
             Chat.setChatConversationMode(true);
1173 1311
         }
@@ -1238,7 +1376,7 @@ var VideoLayout = (function (my) {
1238 1376
      */
1239 1377
     my.showModeratorIndicator = function () {
1240 1378
 
1241
-        var isModerator = Moderator.isModerator();
1379
+        var isModerator = xmpp.isModerator();
1242 1380
         if (isModerator) {
1243 1381
             var indicatorSpan = $('#localVideoContainer .focusindicator');
1244 1382
 
@@ -1247,7 +1385,10 @@ var VideoLayout = (function (my) {
1247 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 1393
             if (Strophe.getResourceFromJid(jid) === 'focus') {
1253 1394
                 // Skip server side focus
@@ -1263,7 +1404,7 @@ var VideoLayout = (function (my) {
1263 1404
                 return;
1264 1405
             }
1265 1406
 
1266
-            var member = connection.emuc.members[jid];
1407
+            var member = members[jid];
1267 1408
 
1268 1409
             if (member.role === 'moderator') {
1269 1410
                 // Remove menu if peer is moderator
@@ -1435,7 +1576,7 @@ var VideoLayout = (function (my) {
1435 1576
         var videoSpanId = null;
1436 1577
         var videoContainerId = null;
1437 1578
         if (resourceJid
1438
-                === Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
1579
+                === xmpp.myResource()) {
1439 1580
             videoSpanId = 'localVideoWrapper';
1440 1581
             videoContainerId = 'localVideoContainer';
1441 1582
         }
@@ -1478,7 +1619,7 @@ var VideoLayout = (function (my) {
1478 1619
             }
1479 1620
 
1480 1621
             Avatar.showUserAvatar(
1481
-                connection.emuc.findJidFromResource(resourceJid));
1622
+                xmpp.findJidFromResource(resourceJid));
1482 1623
         }
1483 1624
     };
1484 1625
 
@@ -1603,7 +1744,7 @@ var VideoLayout = (function (my) {
1603 1744
                     lastNPickupJid = jid;
1604 1745
                     $(document).trigger("pinnedendpointchanged", [jid]);
1605 1746
                 }
1606
-            } else if (jid == connection.emuc.myroomjid) {
1747
+            } else if (jid == xmpp.myJid()) {
1607 1748
                 $("#localVideoContainer").click();
1608 1749
             }
1609 1750
         }
@@ -1615,13 +1756,13 @@ var VideoLayout = (function (my) {
1615 1756
     $(document).bind('audiomuted.muc', function (event, jid, isMuted) {
1616 1757
         /*
1617 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 1761
             // The local mute indicator is controlled locally
1621 1762
             return;
1622 1763
         }*/
1623 1764
         var videoSpanId = null;
1624
-        if (jid === connection.emuc.myroomjid) {
1765
+        if (jid === xmpp.myJid()) {
1625 1766
             videoSpanId = 'localVideoContainer';
1626 1767
         } else {
1627 1768
             VideoLayout.ensurePeerContainerExists(jid);
@@ -1630,7 +1771,7 @@ var VideoLayout = (function (my) {
1630 1771
 
1631 1772
         mutedAudios[jid] = isMuted;
1632 1773
 
1633
-        if (Moderator.isModerator()) {
1774
+        if (xmpp.isModerator()) {
1634 1775
             VideoLayout.updateRemoteVideoMenu(jid, isMuted);
1635 1776
         }
1636 1777
 
@@ -1648,7 +1789,7 @@ var VideoLayout = (function (my) {
1648 1789
 
1649 1790
         Avatar.showUserAvatar(jid, isMuted);
1650 1791
         var videoSpanId = null;
1651
-        if (jid === connection.emuc.myroomjid) {
1792
+        if (jid === xmpp.myJid()) {
1652 1793
             videoSpanId = 'localVideoContainer';
1653 1794
         } else {
1654 1795
             VideoLayout.ensurePeerContainerExists(jid);
@@ -1662,11 +1803,11 @@ var VideoLayout = (function (my) {
1662 1803
     /**
1663 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 1808
         var name = null;
1668 1809
         if (jid === 'localVideoContainer'
1669
-            || jid === connection.emuc.myroomjid) {
1810
+            || jid === xmpp.myJid()) {
1670 1811
             name = nickname;
1671 1812
             setDisplayName('localVideoContainer',
1672 1813
                            displayName);
@@ -1680,10 +1821,10 @@ var VideoLayout = (function (my) {
1680 1821
         }
1681 1822
 
1682 1823
         if(jid === 'localVideoContainer')
1683
-            jid = connection.emuc.myroomjid;
1824
+            jid = xmpp.myJid();
1684 1825
         if(!name || name != displayName)
1685 1826
             API.triggerEvent("displayNameChange",{jid: jid, displayname: displayName});
1686
-    });
1827
+    };
1687 1828
 
1688 1829
     /**
1689 1830
      * On dominant speaker changed event.
@@ -1691,7 +1832,7 @@ var VideoLayout = (function (my) {
1691 1832
     $(document).bind('dominantspeakerchanged', function (event, resourceJid) {
1692 1833
         // We ignore local user events.
1693 1834
         if (resourceJid
1694
-                === Strophe.getResourceFromJid(connection.emuc.myroomjid))
1835
+                === xmpp.myResource())
1695 1836
             return;
1696 1837
 
1697 1838
         // Update the current dominant speaker.
@@ -1822,7 +1963,7 @@ var VideoLayout = (function (my) {
1822 1963
                 if (!isVisible) {
1823 1964
                     console.log("Add to last N", resourceJid);
1824 1965
 
1825
-                    var jid = connection.emuc.findJidFromResource(resourceJid);
1966
+                    var jid = xmpp.findJidFromResource(resourceJid);
1826 1967
                     var mediaStream = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
1827 1968
                     var sel = $('#participant_' + resourceJid + '>video');
1828 1969
 
@@ -1855,7 +1996,7 @@ var VideoLayout = (function (my) {
1855 1996
 
1856 1997
             var resource, container, src;
1857 1998
             var myResource
1858
-                = Strophe.getResourceFromJid(connection.emuc.myroomjid);
1999
+                = xmpp.myResource();
1859 2000
 
1860 2001
             // Find out which endpoint to show in the large video.
1861 2002
             for (var i = 0; i < lastNEndpoints.length; i++) {
@@ -1879,37 +2020,6 @@ var VideoLayout = (function (my) {
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 2023
     $(document).bind('simulcastlayerschanging', function (event, endpointSimulcastLayers) {
1914 2024
         endpointSimulcastLayers.forEach(function (esl) {
1915 2025
 
@@ -1930,13 +2040,13 @@ var VideoLayout = (function (my) {
1930 2040
 
1931 2041
             // Get session and stream from primary ssrc.
1932 2042
             var res = simulcast.getReceivingVideoStreamBySSRC(primarySSRC);
1933
-            var session = res.session;
2043
+            var sid = res.sid;
1934 2044
             var electedStream = res.stream;
1935 2045
 
1936
-            if (session && electedStream) {
2046
+            if (sid && electedStream) {
1937 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 2051
                 var msidParts = msid.split(' ');
1942 2052
 
@@ -1956,7 +2066,7 @@ var VideoLayout = (function (my) {
1956 2066
                 }
1957 2067
 
1958 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,17 +2098,17 @@ var VideoLayout = (function (my) {
1988 2098
 
1989 2099
             // Get session and stream from primary ssrc.
1990 2100
             var res = simulcast.getReceivingVideoStreamBySSRC(primarySSRC);
1991
-            var session = res.session;
2101
+            var sid = res.sid;
1992 2102
             var electedStream = res.stream;
1993 2103
 
1994
-            if (session && electedStream) {
2104
+            if (sid && electedStream) {
1995 2105
                 var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC);
1996 2106
 
1997 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 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 2113
                 var updateLargeVideo = (Strophe.getResourceFromJid(ssrc2jid[primarySSRC])
2004 2114
                     == largeVideoState.userResourceJid);
@@ -2035,7 +2145,7 @@ var VideoLayout = (function (my) {
2035 2145
                 }
2036 2146
 
2037 2147
                 var videoId;
2038
-                if(resource == Strophe.getResourceFromJid(connection.emuc.myroomjid))
2148
+                if(resource == xmpp.myResource())
2039 2149
                 {
2040 2150
                     videoId = "localVideoContainer";
2041 2151
                 }
@@ -2048,7 +2158,7 @@ var VideoLayout = (function (my) {
2048 2158
                     connectionIndicator.updatePopoverData();
2049 2159
 
2050 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,8 +2173,8 @@ var VideoLayout = (function (my) {
2063 2173
         if(object.resolution !== null)
2064 2174
         {
2065 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 2179
         updateStatsIndicator("localVideoContainer", percent, object);
2070 2180
         for(var jid in resolution)

+ 1
- 2
modules/connectionquality/connectionquality.js Wyświetl plik

@@ -29,8 +29,7 @@ function startSendingStats() {
29 29
  * Sends statistics to other participants
30 30
  */
31 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 Wyświetl plik

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

+ 32
- 71
modules/simulcast/SimulcastReceiver.js Wyświetl plik

@@ -159,43 +159,19 @@ SimulcastReceiver.prototype.getReceivingSSRC = function (jid) {
159 159
 
160 160
     // If we haven't receiving a "changed" event yet, then we must be receiving
161 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,47 +182,32 @@ SimulcastReceiver.prototype.getReceivingSSRC = function (jid) {
206 182
 
207 183
 SimulcastReceiver.prototype.getReceivingVideoStreamBySSRC = function (ssrc)
208 184
 {
209
-    var session, electedStream;
185
+    var sid, electedStream;
210 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 209
     return {
249
-        session: session,
210
+        sid: sid,
250 211
         stream: electedStream
251 212
     };
252 213
 };

+ 3
- 24
modules/statistics/RTPStatsCollector.js Wyświetl plik

@@ -329,30 +329,9 @@ StatsCollector.prototype.addStatsToBeLogged = function (reports) {
329 329
 };
330 330
 
331 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 335
     // Reset the stats
357 336
     this.statsToBeLogged.stats = {};
358 337
     this.statsToBeLogged.timestamps = [];
@@ -700,7 +679,7 @@ StatsCollector.prototype.processAudioLevelReport = function ()
700 679
             // but it seems to vary between 0 and around 32k.
701 680
             audioLevel = audioLevel / 32767;
702 681
             jidStats.setSsrcAudioLevel(ssrc, audioLevel);
703
-            if(jid != connection.emuc.myroomjid)
682
+            if(jid != xmpp.myJid())
704 683
                 this.eventEmitter.emit("statistics.audioLevel", jid, audioLevel);
705 684
         }
706 685
 

+ 9
- 8
modules/statistics/statistics.js Wyświetl plik

@@ -59,6 +59,14 @@ function onStreamCreated(stream)
59 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 71
 var statistics =
64 72
 {
@@ -117,19 +125,12 @@ var statistics =
117 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 128
     start: function () {
129 129
         this.addConnectionStatsListener(connectionquality.updateLocalStats);
130 130
         this.addRemoteStatsStopListener(connectionquality.stopSendingStats);
131 131
         RTC.addStreamListener(onStreamCreated,
132 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 Wyświetl plik

@@ -1,6 +1,11 @@
1 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 7
 // Jingle stuff
3
-function JingleSession(me, sid, connection) {
8
+function JingleSession(me, sid, connection, service) {
4 9
     this.me = me;
5 10
     this.sid = sid;
6 11
     this.connection = connection;
@@ -12,13 +17,13 @@ function JingleSession(me, sid, connection) {
12 17
     this.localSDP = null;
13 18
     this.remoteSDP = null;
14 19
     this.relayedStreams = [];
15
-    this.remoteStreams = [];
16 20
     this.startTime = null;
17 21
     this.stopTime = null;
18 22
     this.media_constraints = null;
19 23
     this.pc_constraints = null;
20 24
     this.ice_config = {};
21 25
     this.drip_container = [];
26
+    this.service = service;
22 27
 
23 28
     this.usetrickle = true;
24 29
     this.usepranswer = false; // early transport warmup -- mind you, this might fail. depends on webrtc issue 1718
@@ -73,16 +78,11 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) {
73 78
         self.sendIceCandidate(event.candidate);
74 79
     };
75 80
     this.peerconnection.onaddstream = function (event) {
76
-        self.remoteStreams.push(event.stream);
77 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 84
     this.peerconnection.onremovestream = function (event) {
81 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 86
         // FIXME: remotestreamremoved.jingle not defined anywhere(unused)
87 87
         $(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
88 88
     };
@@ -99,7 +99,7 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) {
99 99
                 this.stopTime = new Date();
100 100
                 break;
101 101
         }
102
-        $(document).trigger('iceconnectionstatechange.jingle', [self.sid, self]);
102
+        onIceConnectionStateChange(self.sid, self);
103 103
     };
104 104
     // add any local and relayed stream
105 105
     RTC.localStreams.forEach(function(stream) {
@@ -110,6 +110,49 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) {
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 156
 JingleSession.prototype.accept = function () {
114 157
     var self = this;
115 158
     this.state = 'active';
@@ -145,12 +188,13 @@ JingleSession.prototype.accept = function () {
145 188
         // FIXME: change any inactive to sendrecv or whatever they were originally
146 189
         sdp = sdp.replace('a=inactive', 'a=sendrecv');
147 190
     }
191
+    var self = this;
148 192
     this.peerconnection.setLocalDescription(new RTCSessionDescription({type: 'answer', sdp: sdp}),
149 193
         function () {
150 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 198
                 function () {
155 199
                     var ack = {};
156 200
                     ack.source = 'answer';
@@ -347,8 +391,8 @@ JingleSession.prototype.createdOffer = function (sdp) {
347 391
                 action: 'session-initiate',
348 392
                 initiator: this.initiator,
349 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 396
             function () {
353 397
                 var ack = {};
354 398
                 ack.source = 'offer';
@@ -369,13 +413,11 @@ JingleSession.prototype.createdOffer = function (sdp) {
369 413
     sdp.sdp = this.localSDP.raw;
370 414
     this.peerconnection.setLocalDescription(sdp,
371 415
         function () {
372
-            if(this.usetrickle)
416
+            if(self.usetrickle)
373 417
             {
374 418
                 sendJingle();
375
-                $(document).trigger('setLocalDescription.jingle', [self.sid]);
376 419
             }
377
-            else
378
-                $(document).trigger('setLocalDescription.jingle', [self.sid]);
420
+            self.setLocalDescription();
379 421
             //console.log('setLocalDescription success');
380 422
         },
381 423
         function (e) {
@@ -587,7 +629,7 @@ JingleSession.prototype.createdAnswer = function (sdp, provisional) {
587 629
                 var publicLocalDesc = simulcast.reverseTransformLocalDescription(sdp);
588 630
                 var publicLocalSDP = new SDP(publicLocalDesc.sdp);
589 631
                 publicLocalSDP.toJingle(accept, self.initiator == self.me ? 'initiator' : 'responder', ssrcs);
590
-                this.connection.sendIQ(accept,
632
+                self.connection.sendIQ(accept,
591 633
                     function () {
592 634
                         var ack = {};
593 635
                         ack.source = 'answer';
@@ -610,10 +652,8 @@ JingleSession.prototype.createdAnswer = function (sdp, provisional) {
610 652
             //console.log('setLocalDescription success');
611 653
             if (self.usetrickle && !self.usepranswer) {
612 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 658
         function (e) {
619 659
             console.error('setLocalDescription failed', e);
@@ -799,7 +839,7 @@ JingleSession.prototype.modifySources = function (successCallback) {
799 839
     if (this.peerconnection.signalingState == 'closed') return;
800 840
     if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
801 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 843
         if(successCallback){
804 844
             successCallback();
805 845
         }
@@ -889,7 +929,7 @@ JingleSession.prototype.modifySources = function (successCallback) {
889 929
                     self.peerconnection.setLocalDescription(modifiedAnswer,
890 930
                         function() {
891 931
                             //console.log('modified setLocalDescription ok');
892
-                            $(document).trigger('setLocalDescription.jingle', [self.sid]);
932
+                            self.setLocalDescription();
893 933
                             if(successCallback){
894 934
                                 successCallback();
895 935
                             }
@@ -1064,12 +1104,20 @@ JingleSession.prototype.setVideoMute = function (mute, callback, options) {
1064 1104
     } else if (this.videoMuteByUser) {
1065 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 1115
     if (mute == RTC.localVideo.isMuted())
1068 1116
     {
1069 1117
         // Even if no change occurs, the specified callback is to be executed.
1070 1118
         // The specified callback may, optionally, return a successCallback
1071 1119
         // which is to be executed as well.
1072
-        var successCallback = callback(mute);
1120
+        var successCallback = localCallback(mute);
1073 1121
 
1074 1122
         if (successCallback) {
1075 1123
             successCallback();
@@ -1079,14 +1127,14 @@ JingleSession.prototype.setVideoMute = function (mute, callback, options) {
1079 1127
 
1080 1128
         this.hardMuteVideo(mute);
1081 1129
 
1082
-        this.modifySources(callback(mute));
1130
+        this.modifySources(localCallback(mute));
1083 1131
     }
1084 1132
 };
1085 1133
 
1086 1134
 // SDP-based mute by going recvonly/sendrecv
1087 1135
 // FIXME: should probably black out the screen as well
1088 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 1140
 JingleSession.prototype.hardMuteVideo = function (muted) {
@@ -1172,8 +1220,170 @@ JingleSession.onJingleError = function (session, error)
1172 1220
 
1173 1221
 JingleSession.onJingleFatalError = function (session, error)
1174 1222
 {
1175
-    sessionTerminated = true;
1223
+    this.service.sessionTerminated = true;
1176 1224
     connection.emuc.doLeave();
1177 1225
     UI.messageHandler.showError(  "Sorry",
1178 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 Wyświetl plik

@@ -1,4 +1,6 @@
1 1
 /* jshint -W117 */
2
+var SDPUtil = require("./SDPUtil");
3
+
2 4
 // SDP STUFF
3 5
 function SDP(sdp) {
4 6
     this.media = sdp.split('\r\nm=');
@@ -71,169 +73,6 @@ SDP.prototype.containsSSRC = function(ssrc) {
71 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 77
 // remove iSAC and CN from SDP
239 78
 SDP.prototype.mangle = function () {
@@ -776,352 +615,6 @@ SDP.prototype.jingle2media = function (content) {
776 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 Wyświetl plik

@@ -0,0 +1,165 @@
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 Wyświetl plik

@@ -0,0 +1,349 @@
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 Wyświetl plik

@@ -262,3 +262,5 @@ TraceablePeerConnection.prototype.getStats = function(callback, errback) {
262 262
     }
263 263
 };
264 264
 
265
+module.exports = TraceablePeerConnection;
266
+

moderator.js → modules/xmpp/moderator.js Wyświetl plik

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

@@ -0,0 +1,152 @@
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 Wyświetl plik

@@ -0,0 +1,607 @@
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 Wyświetl plik

@@ -0,0 +1,334 @@
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 Wyświetl plik

@@ -0,0 +1,20 @@
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 Wyświetl plik

@@ -0,0 +1,58 @@
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 Wyświetl plik

@@ -0,0 +1,95 @@
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 Wyświetl plik

@@ -0,0 +1,42 @@
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 Wyświetl plik

@@ -0,0 +1,422 @@
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 Wyświetl plik

@@ -1,548 +0,0 @@
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 Wyświetl plik

@@ -1,167 +0,0 @@
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 Wyświetl plik

@@ -0,0 +1,14 @@
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;

Ładowanie…
Anuluj
Zapisz