Bladeren bron

Merge branch 'master' into ice_restart

# Conflicts:
#	modules/xmpp/JingleSessionPC.js
dev1
paweldomas 9 jaren geleden
bovenliggende
commit
ffca0d2777

+ 15
- 7
JitsiConference.js Bestand weergeven

@@ -673,7 +673,7 @@ JitsiConference.prototype.isRecordingSupported = function () {
673 673
 JitsiConference.prototype.getRecordingState = function () {
674 674
     if(this.room)
675 675
         return this.room.getRecordingState();
676
-    return "off";
676
+    return Recording.status.OFF;
677 677
 }
678 678
 
679 679
 /**
@@ -692,10 +692,10 @@ JitsiConference.prototype.toggleRecording = function (options) {
692 692
     if(this.room)
693 693
         return this.room.toggleRecording(options, function (status, error) {
694 694
             this.eventEmitter.emit(
695
-                JitsiConferenceEvents.RECORDING_STATE_CHANGED, status, error);
695
+                JitsiConferenceEvents.RECORDER_STATE_CHANGED, status, error);
696 696
         }.bind(this));
697 697
     this.eventEmitter.emit(
698
-        JitsiConferenceEvents.RECORDING_STATE_CHANGED, "error",
698
+        JitsiConferenceEvents.RECORDER_STATE_CHANGED, "error",
699 699
         new Error("The conference is not created yet!"));
700 700
 }
701 701
 
@@ -822,6 +822,13 @@ JitsiConference.prototype.getLogs = function () {
822 822
     return data;
823 823
 };
824 824
 
825
+/**
826
+ * Returns measured performanceTimes.
827
+ */
828
+JitsiConference.prototype.getPerformanceTimes = function () {
829
+    return this.room.performanceTimes;
830
+};
831
+
825 832
 /**
826 833
  * Sends the given feedback through CallStats if enabled.
827 834
  *
@@ -851,11 +858,12 @@ JitsiConference.prototype.isCallstatsEnabled = function () {
851 858
  */
852 859
 function setupListeners(conference) {
853 860
     conference.xmpp.addListener(
854
-        XMPPEvents.CALL_INCOMING, function (jingleSession, jingleOffer) {
861
+        XMPPEvents.CALL_INCOMING, function (jingleSession, jingleOffer, now) {
855 862
 
856 863
         if (conference.room.isFocus(jingleSession.peerjid)) {
857 864
             // Accept incoming call
858 865
             conference.room.setJingleSession(jingleSession);
866
+            conference.room.performanceTimes["session.initiate"] = now;
859 867
             jingleSession.initialize(false /* initiator */, conference.room);
860 868
             conference.rtc.onIncommingCall(jingleSession);
861 869
             jingleSession.acceptOffer(jingleOffer, null,
@@ -994,10 +1002,10 @@ function setupListeners(conference) {
994 1002
         conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_INTERRUPTED);
995 1003
     });
996 1004
 
997
-    conference.room.addListener(XMPPEvents.RECORDING_STATE_CHANGED,
998
-        function () {
1005
+    conference.room.addListener(XMPPEvents.RECORDER_STATE_CHANGED,
1006
+        function (state) {
999 1007
             conference.eventEmitter.emit(
1000
-                JitsiConferenceEvents.RECORDING_STATE_CHANGED);
1008
+                JitsiConferenceEvents.RECORDER_STATE_CHANGED, state);
1001 1009
         });
1002 1010
 
1003 1011
     conference.room.addListener(XMPPEvents.PHONE_NUMBER_CHANGED, function () {

+ 1
- 1
JitsiConferenceEvents.js Bestand weergeven

@@ -111,7 +111,7 @@ var JitsiConferenceEvents = {
111 111
     /**
112 112
      * Indicates that recording state changed.
113 113
      */
114
-    RECORDING_STATE_CHANGED: "conference.recordingStateChanged",
114
+    RECORDER_STATE_CHANGED: "conference.recorderStateChanged",
115 115
     /**
116 116
      * Indicates that phone number changed.
117 117
      */

+ 21
- 2
JitsiConnection.js Bestand weergeven

@@ -13,13 +13,14 @@ function JitsiConnection(appID, token, options) {
13 13
     this.appID = appID;
14 14
     this.token = token;
15 15
     this.options = options;
16
-    this.xmpp = new XMPP(options);
16
+    this.xmpp = new XMPP(options, token);
17 17
     this.conferences = {};
18 18
 }
19 19
 
20 20
 /**
21 21
  * Connect the client with the server.
22
- * @param options {object} connecting options (for example authentications parameters).
22
+ * @param options {object} connecting options
23
+ * (for example authentications parameters).
23 24
  */
24 25
 JitsiConnection.prototype.connect = function (options) {
25 26
     if(!options)
@@ -28,6 +29,17 @@ JitsiConnection.prototype.connect = function (options) {
28 29
     this.xmpp.connect(options.id, options.password);
29 30
 }
30 31
 
32
+/**
33
+ * Attach to existing connection. Can be used for optimizations. For example:
34
+ * if the connection is created on the server we can attach to it and start
35
+ * using it.
36
+ *
37
+ * @param options {object} connecting options - rid, sid and jid.
38
+ */
39
+JitsiConnection.prototype.attach = function (options) {
40
+    this.xmpp.attach(options);
41
+}
42
+
31 43
 /**
32 44
  * Disconnect the client from the server.
33 45
  */
@@ -81,4 +93,11 @@ JitsiConnection.prototype.removeEventListener = function (event, listener) {
81 93
     this.xmpp.removeListener(event, listener);
82 94
 }
83 95
 
96
+/**
97
+ * Returns measured performanceTimes.
98
+ */
99
+JitsiConnection.prototype.getPerformanceTimes = function () {
100
+    return this.xmpp.performanceTimes;
101
+};
102
+
84 103
 module.exports = JitsiConnection;

+ 23
- 0
JitsiRecorderErrors.js Bestand weergeven

@@ -0,0 +1,23 @@
1
+/**
2
+ * Enumeration with the errors for the conference.
3
+ * @type {{string: string}}
4
+ */
5
+var JitsiRecorderErrors = {
6
+    /**
7
+     * Indicates that the recorder is currently unavailable.
8
+     */
9
+    RECORDER_UNAVAILABLE: "recorder.unavailable",
10
+
11
+    /**
12
+     * Indicates that the authentication token is missing.
13
+     */
14
+    NO_TOKEN: "recorder.noToken",
15
+
16
+    /**
17
+     * Indicates that a state change failed.
18
+     */
19
+    STATE_CHANGE_FAILED: "recorder.stateChangeFailed",
20
+
21
+};
22
+
23
+module.exports = JitsiRecorderErrors;

+ 6
- 0
README.md Bestand weergeven

@@ -21,5 +21,11 @@ To build the Lib Jitsi Meet, just type
21 21
 npm install
22 22
 ```
23 23
 
24
+For development, use watch to recompile your browserify bundle on file changes:
25
+
26
+```
27
+npm run watch
28
+```
29
+
24 30
 ## Discuss
25 31
 Please use the [Jitsi dev mailing list](http://lists.jitsi.org/pipermail/dev/) to discuss feature requests before opening an issue on Github. 

+ 61
- 0
connection_optimization/external_connect.js Bestand weergeven

@@ -0,0 +1,61 @@
1
+/**
2
+ * Requests the given webservice that will create the connection and will return
3
+ * the necessary details(rid, sid and jid) to attach to this connection and
4
+ * start using it. This script can be used for optimizing the connection startup
5
+ * time. The function will send AJAX request to a webservice that should
6
+ * create the bosh session much faster than the client because the webservice
7
+ * can be started on the same machine as the XMPP serever.
8
+ *
9
+ * NOTE: It's vert important to execute this function as early as you can for
10
+ * optimal results.
11
+ *
12
+ * @param webserviceUrl the url for the web service that is going to create the
13
+ * connection.
14
+ * @param success_callback callback function called with the result of the AJAX
15
+ * request if the request was successfull. The callback will receive one
16
+ * parameter which will be JS Object with properties - rid, sid and jid. This
17
+ * result should be passed to JitsiConnection.attach method in order to use that
18
+ * connection.
19
+ * @param error_callback callback function called the AJAX request fail. This
20
+ * callback is going to receive one parameter which is going to be JS error
21
+ * object with a reason for failure in it.
22
+ */
23
+function createConnectionExternally(webserviceUrl, success_callback,
24
+    error_callback) {
25
+    if (!window.XMLHttpRequest) {
26
+        error_callback(new Error("XMLHttpRequest is not supported!"));
27
+        return;
28
+    }
29
+
30
+    var HTTP_STATUS_OK = 200;
31
+
32
+    var xhttp = new XMLHttpRequest();
33
+
34
+    xhttp.onreadystatechange = function() {
35
+        if (xhttp.readyState == xhttp.DONE) {
36
+            var now = window.performanceTimes["external_connect.done"] =
37
+                window.performance.now();
38
+            console.log("(TIME) external connect XHR done:\t", now);
39
+            if (xhttp.status == HTTP_STATUS_OK) {
40
+                try {
41
+                    var data = JSON.parse(xhttp.responseText);
42
+                    success_callback(data);
43
+                } catch (e) {
44
+                    error_callback(e);
45
+                }
46
+            } else {
47
+                error_callback(new Error("XMLHttpRequest error. Status: " +
48
+                    xhttp.status + ". Error message: " + xhttp.statusText));
49
+            }
50
+        }
51
+    };
52
+
53
+    xhttp.timeout = 3000;
54
+
55
+    xhttp.open("GET", webserviceUrl, true);
56
+    window.performanceTimes = {};
57
+    var now = window.performanceTimes["external_connect.sending"] =
58
+        window.performance.now();
59
+    console.log("(TIME) Sending external connect XHR:\t", now);
60
+    xhttp.send();
61
+}

+ 1
- 1
doc/example/example.js Bestand weergeven

@@ -121,7 +121,7 @@ function onConnectionSuccess(){
121 121
       function(userID, audioLevel){
122 122
           console.log(userID + " - " + audioLevel);
123 123
       });
124
-    room.on(JitsiMeetJS.events.conference.RECORDING_STATE_CHANGED, function () {
124
+    room.on(JitsiMeetJS.events.conference.RECORDER_STATE_CHANGED, function () {
125 125
         console.log(room.isRecordingSupported() + " - " +
126 126
             room.getRecordingState() + " - " +
127 127
             room.getRecordingURL());

+ 1
- 1
doc/tokens.md Bestand weergeven

@@ -61,7 +61,7 @@ just install 'jitsi-meet-tokens' on top of it. In order to have it configured au
61 61
 jitsi-meet is required which comes with special Prosody config template.
62 62
 
63 63
 ```
64
-apt-get install jitsi-meet-token
64
+apt-get install jitsi-meet-tokens
65 65
 ```
66 66
 
67 67
 Proceed to "Patching Prosody" section to finish configuration.

+ 47
- 31
modules/xmpp/ChatRoom.js Bestand weergeven

@@ -83,6 +83,7 @@ function ChatRoom(connection, jid, password, XMPP, options, settings) {
83 83
     this.lastPresences = {};
84 84
     this.phoneNumber = null;
85 85
     this.phonePin = null;
86
+    this.performanceTimes = {};
86 87
 }
87 88
 
88 89
 ChatRoom.prototype.initPresenceMap = function () {
@@ -117,19 +118,19 @@ ChatRoom.prototype.join = function (password) {
117 118
     if(password)
118 119
         this.password = password;
119 120
     var self = this;
120
-    this.moderator.allocateConferenceFocus(function()
121
-    {
121
+    this.moderator.allocateConferenceFocus(function () {
122 122
         self.sendPresence(true);
123
-    }.bind(this));
123
+    });
124 124
 };
125 125
 
126 126
 ChatRoom.prototype.sendPresence = function (fromJoin) {
127
-    if (!this.presMap['to'] || (!this.joined && !fromJoin)) {
127
+    var to = this.presMap['to'];
128
+    if (!to || (!this.joined && !fromJoin)) {
128 129
         // Too early to send presence - not initialized
129 130
         return;
130 131
     }
131 132
 
132
-    var pres = $pres({to: this.presMap['to'] });
133
+    var pres = $pres({to: to });
133 134
     pres.c('x', {xmlns: this.presMap['xns']});
134 135
 
135 136
     if (this.password) {
@@ -139,13 +140,22 @@ ChatRoom.prototype.sendPresence = function (fromJoin) {
139 140
     pres.up();
140 141
 
141 142
     // Send XEP-0115 'c' stanza that contains our capabilities info
142
-    if (this.connection.caps) {
143
-        this.connection.caps.node = this.xmpp.options.clientNode;
144
-        pres.c('c', this.connection.caps.generateCapsAttrs()).up();
143
+    var connection = this.connection;
144
+    var caps = connection.caps;
145
+    if (caps) {
146
+        caps.node = this.xmpp.options.clientNode;
147
+        pres.c('c', caps.generateCapsAttrs()).up();
145 148
     }
146 149
 
147 150
     parser.JSON2packet(this.presMap.nodes, pres);
148
-    this.connection.send(pres);
151
+    connection.send(pres);
152
+    if (fromJoin) {
153
+        // XXX We're pressed for time here because we're beginning a complex
154
+        // and/or lengthy conference-establishment process which supposedly
155
+        // involves multiple RTTs. We don't have the time to wait for Strophe to
156
+        // decide to send our IQ.
157
+        connection.flush();
158
+    }
149 159
 };
150 160
 
151 161
 
@@ -215,17 +225,16 @@ ChatRoom.prototype.onPresence = function (pres) {
215 225
     var member = {};
216 226
     member.show = $(pres).find('>show').text();
217 227
     member.status = $(pres).find('>status').text();
218
-    var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item');
219
-    member.affiliation = tmp.attr('affiliation');
220
-    member.role = tmp.attr('role');
228
+    var mucUserItem
229
+        = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item');
230
+    member.affiliation = mucUserItem.attr('affiliation');
231
+    member.role = mucUserItem.attr('role');
221 232
 
222 233
     // Focus recognition
223
-    member.jid = tmp.attr('jid');
224
-    member.isFocus = false;
225
-    if (member.jid
226
-        && member.jid.indexOf(this.moderator.getFocusUserJid() + "/") === 0) {
227
-        member.isFocus = true;
228
-    }
234
+    var jid = mucUserItem.attr('jid');
235
+    member.jid = jid;
236
+    member.isFocus
237
+        = !!jid && jid.indexOf(this.moderator.getFocusUserJid() + "/") === 0;
229 238
 
230 239
     $(pres).find(">x").remove();
231 240
     var nodes = [];
@@ -249,15 +258,15 @@ ChatRoom.prototype.onPresence = function (pres) {
249 258
     }
250 259
 
251 260
     if (from == this.myroomjid) {
252
-        if (member.affiliation == 'owner'
253
-            &&  this.role !== member.role) {
254
-                this.role = member.role;
255
-                this.eventEmitter.emit(
256
-                    XMPPEvents.LOCAL_ROLE_CHANGED, this.role);
261
+        if (member.affiliation == 'owner' && this.role !== member.role) {
262
+            this.role = member.role;
263
+            this.eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED, this.role);
257 264
         }
258 265
         if (!this.joined) {
259 266
             this.joined = true;
260
-            console.log("(TIME) MUC joined:\t", window.performance.now());
267
+            var now = this.performanceTimes["muc.joined"] =
268
+                window.performance.now();
269
+            console.log("(TIME) MUC joined:\t", now);
261 270
             this.eventEmitter.emit(XMPPEvents.MUC_JOINED);
262 271
         }
263 272
     } else if (this.members[from] === undefined) {
@@ -273,7 +282,7 @@ ChatRoom.prototype.onPresence = function (pres) {
273 282
                 if(this.lastJibri)
274 283
                     this.recording.handleJibriPresence(this.lastJibri);
275 284
             }
276
-            logger.info("Ignore focus: " + from + ", real JID: " + member.jid);
285
+            logger.info("Ignore focus: " + from + ", real JID: " + jid);
277 286
         }
278 287
         else {
279 288
             this.eventEmitter.emit(
@@ -282,15 +291,16 @@ ChatRoom.prototype.onPresence = function (pres) {
282 291
     } else {
283 292
         // Presence update for existing participant
284 293
         // Watch role change:
285
-        if (this.members[from].role != member.role) {
286
-            this.members[from].role = member.role;
294
+        var memberOfThis = this.members[from];
295
+        if (memberOfThis.role != member.role) {
296
+            memberOfThis.role = member.role;
287 297
             this.eventEmitter.emit(
288 298
                 XMPPEvents.MUC_ROLE_CHANGED, from, member.role);
289 299
         }
290 300
 
291 301
         // store the new display name
292 302
         if(member.displayName)
293
-            this.members[from].displayName = member.displayName;
303
+            memberOfThis.displayName = member.displayName;
294 304
     }
295 305
 
296 306
     // after we had fired member or room joined events, lets fire events
@@ -344,13 +354,19 @@ ChatRoom.prototype.onPresence = function (pres) {
344 354
         if(this.recording)
345 355
             this.recording.handleJibriPresence(jibri);
346 356
     }
347
-
348 357
 };
349 358
 
350 359
 ChatRoom.prototype.processNode = function (node, from) {
351
-    if(this.presHandlers[node.tagName])
352
-        this.presHandlers[node.tagName](
360
+    // make sure we catch all errors coming from any handler
361
+    // otherwise we can remove the presence handler from strophe
362
+    try {
363
+        if(this.presHandlers[node.tagName])
364
+            this.presHandlers[node.tagName](
353 365
                 node, Strophe.getResourceFromJid(from), from);
366
+    } catch (e) {
367
+        logger.error('Error processing:' + node.tagName
368
+            + ' node.', e);
369
+    }
354 370
 };
355 371
 
356 372
 ChatRoom.prototype.sendMessage = function (body, nickname) {

+ 77
- 2
modules/xmpp/JingleSessionPC.js Bestand weergeven

@@ -152,8 +152,13 @@ JingleSessionPC.prototype.doInitialize = function () {
152 152
      */
153 153
     this.peerconnection.oniceconnectionstatechange = function (event) {
154 154
         if (!(self && self.peerconnection)) return;
155
+        self.room.performanceTimes["ice.state"] =
156
+            self.room.performanceTimes["ice.state"] || [];
157
+        var now = window.performance.now();
158
+        self.room.performanceTimes["ice.state"].push(
159
+            {state: self.peerconnection.iceConnectionState, time: now});
155 160
         logger.log("(TIME) ICE " + self.peerconnection.iceConnectionState +
156
-                    ":\t", window.performance.now());
161
+                    ":\t", now);
157 162
         self.updateModifySourcesQueue();
158 163
         switch (self.peerconnection.iceConnectionState) {
159 164
             case 'connected':
@@ -383,7 +388,14 @@ JingleSessionPC.prototype.createAnswer = function (success, failure) {
383 388
     //logger.log('createAnswer');
384 389
     var self = this;
385 390
     this.peerconnection.createAnswer(
386
-        success,
391
+        function (answer) {
392
+            var modifiedAnswer = new SDP(answer.sdp);
393
+            JingleSessionPC._fixAnswerRFC4145Setup(
394
+                /* offer */ self.remoteSDP,
395
+                /* answer */ modifiedAnswer);
396
+            answer.sdp = modifiedAnswer.raw;
397
+            success(answer);
398
+        },
387 399
         function (error) {
388 400
             logger.error("createAnswer failed", error);
389 401
             if (failure)
@@ -425,6 +437,65 @@ JingleSessionPC.prototype.setLocalDescription = function (sdp, success,
425 437
     }
426 438
 };
427 439
 
440
+/**
441
+ * Modifies the values of the setup attributes (defined by
442
+ * {@link http://tools.ietf.org/html/rfc4145#section-4}) of a specific SDP
443
+ * answer in order to overcome a delay of 1 second in the connection
444
+ * establishment between Chrome and Videobridge.
445
+ *
446
+ * @param {SDP} offer - the SDP offer to which the specified SDP answer is
447
+ * being prepared to respond
448
+ * @param {SDP} answer - the SDP to modify
449
+ * @private
450
+ */
451
+JingleSessionPC._fixAnswerRFC4145Setup = function (offer, answer) {
452
+    if (!RTCBrowserType.isChrome()) {
453
+        // It looks like Firefox doesn't agree with the fix (at least in its
454
+        // current implementation) because it effectively remains active even
455
+        // after we tell it to become passive. Apart from Firefox which I tested
456
+        // after the fix was deployed, I tested Chrome only. In order to prevent
457
+        // issues with other browsers, limit the fix to Chrome for the time
458
+        // being.
459
+        return;
460
+    }
461
+
462
+    // XXX Videobridge is the (SDP) offerer and WebRTC (e.g. Chrome) is the
463
+    // answerer (as orchestrated by Jicofo). In accord with
464
+    // http://tools.ietf.org/html/rfc5245#section-5.2 and because both peers
465
+    // are ICE FULL agents, Videobridge will take on the controlling role and
466
+    // WebRTC will take on the controlled role. In accord with
467
+    // https://tools.ietf.org/html/rfc5763#section-5, Videobridge will use the
468
+    // setup attribute value of setup:actpass and WebRTC will be allowed to
469
+    // choose either the setup attribute value of setup:active or
470
+    // setup:passive. Chrome will by default choose setup:active because it is
471
+    // RECOMMENDED by the respective RFC since setup:passive adds additional
472
+    // latency. The case of setup:active allows WebRTC to send a DTLS
473
+    // ClientHello as soon as an ICE connectivity check of its succeeds.
474
+    // Unfortunately, Videobridge will be unable to respond immediately because
475
+    // may not have WebRTC's answer or may have not completed the ICE
476
+    // connectivity establishment. Even more unfortunate is that in the
477
+    // described scenario Chrome's DTLS implementation will insist on
478
+    // retransmitting its ClientHello after a second (the time is in accord
479
+    // with the respective RFC) and will thus cause the whole connection
480
+    // establishment to exceed at least 1 second. To work around Chrome's
481
+    // idiosyncracy, don't allow it to send a ClientHello i.e. change its
482
+    // default choice of setup:active to setup:passive.
483
+    if (offer && answer
484
+            && offer.media && answer.media
485
+            && offer.media.length == answer.media.length) {
486
+        answer.media.forEach(function (a, i) {
487
+            if (SDPUtil.find_line(
488
+                    offer.media[i],
489
+                    'a=setup:actpass',
490
+                    offer.session)) {
491
+                answer.media[i]
492
+                    = a.replace(/a=setup:active/g, 'a=setup:passive');
493
+            }
494
+        });
495
+        answer.raw = answer.session + answer.media.join('');
496
+    }
497
+};
498
+
428 499
 /**
429 500
  * Although it states "replace transport" it does accept full Jingle offer
430 501
  * which should contain new ICE transport details.
@@ -482,6 +553,10 @@ JingleSessionPC.prototype.sendSessionAccept = function (localSDP,
482 553
         success,
483 554
         this.newJingleErrorHandler(accept, failure),
484 555
         IQ_TIMEOUT);
556
+    // XXX Videobridge needs WebRTC's answer (ICE ufrag and pwd, DTLS
557
+    // fingerprint and setup) ASAP in order to start the connection
558
+    // establishment.
559
+    this.connection.flush();
485 560
 };
486 561
 
487 562
 /**

+ 61
- 69
modules/xmpp/SDP.js Bestand weergeven

@@ -5,29 +5,33 @@ var SDPUtil = require("./SDPUtil");
5 5
 
6 6
 // SDP STUFF
7 7
 function SDP(sdp) {
8
-    /**
9
-     * Whether or not to remove TCP ice candidates when translating from/to jingle.
10
-     * @type {boolean}
11
-     */
12
-    this.removeTcpCandidates = false;
13
-
14
-    /**
15
-     * Whether or not to remove UDP ice candidates when translating from/to jingle.
16
-     * @type {boolean}
17
-     */
18
-    this.removeUdpCandidates = false;
19
-
20
-    this.media = sdp.split('\r\nm=');
21
-    for (var i = 1; i < this.media.length; i++) {
22
-        this.media[i] = 'm=' + this.media[i];
23
-        if (i != this.media.length - 1) {
24
-            this.media[i] += '\r\n';
8
+    var media = sdp.split('\r\nm=');
9
+    for (var i = 1, length = media.length; i < length; i++) {
10
+        var media_i = 'm=' + media[i];
11
+        if (i != length - 1) {
12
+            media_i += '\r\n';
25 13
         }
14
+        media[i] = media_i;
26 15
     }
27
-    this.session = this.media.shift() + '\r\n';
28
-    this.raw = this.session + this.media.join('');
16
+    var session = media.shift() + '\r\n';
17
+
18
+    this.media = media;
19
+    this.raw = session + media.join('');
20
+    this.session = session;
29 21
 }
30 22
 
23
+/**
24
+ * Whether or not to remove TCP ice candidates when translating from/to jingle.
25
+ * @type {boolean}
26
+ */
27
+SDP.prototype.removeTcpCandidates = false;
28
+
29
+/**
30
+ * Whether or not to remove UDP ice candidates when translating from/to jingle.
31
+ * @type {boolean}
32
+ */
33
+SDP.prototype.removeUdpCandidates = false;
34
+
31 35
 /**
32 36
  * Returns map of MediaChannel mapped per channel idx.
33 37
  */
@@ -57,7 +61,7 @@ SDP.prototype.getMediaSsrcMap = function() {
57 61
             media.ssrcs[linessrc].lines.push(line);
58 62
         });
59 63
         tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc-group:');
60
-        tmp.forEach(function(line){
64
+        tmp.forEach(function(line) {
61 65
             var idx = line.indexOf(' ');
62 66
             var semantics = line.substr(0, idx).substr(13);
63 67
             var ssrcs = line.substr(14 + semantics.length).split(' ');
@@ -107,13 +111,10 @@ SDP.prototype.mangle = function () {
107 111
                 if (rtpmap.name == 'CN' || rtpmap.name == 'ISAC')
108 112
                     continue;
109 113
                 mline.fmt.push(rtpmap.id);
110
-                newdesc += lines[j] + '\r\n';
111
-            } else {
112
-                newdesc += lines[j] + '\r\n';
113 114
             }
115
+            newdesc += lines[j] + '\r\n';
114 116
         }
115
-        this.media[i] = SDPUtil.build_mline(mline) + '\r\n';
116
-        this.media[i] += newdesc;
117
+        this.media[i] = SDPUtil.build_mline(mline) + '\r\n' + newdesc;
117 118
     }
118 119
     this.raw = this.session + this.media.join('');
119 120
 };
@@ -146,8 +147,8 @@ SDP.prototype.toJingle = function (elem, thecreator) {
146 147
     var self = this;
147 148
     var i, j, k, mline, ssrc, rtpmap, tmp, lines;
148 149
     // new bundle plan
149
-    if (SDPUtil.find_line(this.session, 'a=group:')) {
150
-        lines = SDPUtil.find_lines(this.session, 'a=group:');
150
+    lines = SDPUtil.find_lines(this.session, 'a=group:');
151
+    if (lines.length) {
151 152
         for (i = 0; i < lines.length; i++) {
152 153
             tmp = lines[i].split(' ');
153 154
             var semantics = tmp.shift().substr(8);
@@ -162,20 +163,21 @@ SDP.prototype.toJingle = function (elem, thecreator) {
162 163
         mline = SDPUtil.parse_mline(this.media[i].split('\r\n')[0]);
163 164
         if (!(mline.media === 'audio' ||
164 165
               mline.media === 'video' ||
165
-              mline.media === 'application'))
166
-        {
166
+              mline.media === 'application')) {
167 167
             continue;
168 168
         }
169
-        if (SDPUtil.find_line(this.media[i], 'a=ssrc:')) {
170
-            ssrc = SDPUtil.find_line(this.media[i], 'a=ssrc:').substring(7).split(' ')[0]; // take the first
169
+        var assrcline = SDPUtil.find_line(this.media[i], 'a=ssrc:');
170
+        if (assrcline) {
171
+            ssrc = assrcline.substring(7).split(' ')[0]; // take the first
171 172
         } else {
172 173
             ssrc = false;
173 174
         }
174 175
 
175 176
         elem.c('content', {creator: thecreator, name: mline.media});
176
-        if (SDPUtil.find_line(this.media[i], 'a=mid:')) {
177
+        var amidline = SDPUtil.find_line(this.media[i], 'a=mid:');
178
+        if (amidline) {
177 179
             // prefer identifier from a=mid if present
178
-            var mid = SDPUtil.parse_mid(SDPUtil.find_line(this.media[i], 'a=mid:'));
180
+            var mid = SDPUtil.parse_mid(amidline);
179 181
             elem.attrs({ name: mid });
180 182
         }
181 183
 
@@ -190,8 +192,9 @@ SDP.prototype.toJingle = function (elem, thecreator) {
190 192
                 rtpmap = SDPUtil.find_line(this.media[i], 'a=rtpmap:' + mline.fmt[j]);
191 193
                 elem.c('payload-type', SDPUtil.parse_rtpmap(rtpmap));
192 194
                 // put any 'a=fmtp:' + mline.fmt[j] lines into <param name=foo value=bar/>
193
-                if (SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j])) {
194
-                    tmp = SDPUtil.parse_fmtp(SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j]));
195
+                var afmtpline = SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j]);
196
+                if (afmtpline) {
197
+                    tmp = SDPUtil.parse_fmtp(afmtpline);
195 198
                     for (k = 0; k < tmp.length; k++) {
196 199
                         elem.c('parameter', tmp[k]).up();
197 200
                     }
@@ -200,9 +203,9 @@ SDP.prototype.toJingle = function (elem, thecreator) {
200 203
 
201 204
                 elem.up();
202 205
             }
203
-            if (SDPUtil.find_line(this.media[i], 'a=crypto:', this.session)) {
206
+            var crypto = SDPUtil.find_lines(this.media[i], 'a=crypto:', this.session);
207
+            if (crypto.length) {
204 208
                 elem.c('encryption', {required: 1});
205
-                var crypto = SDPUtil.find_lines(this.media[i], 'a=crypto:', this.session);
206 209
                 crypto.forEach(function(line) {
207 210
                     elem.c('crypto', SDPUtil.parse_crypto(line)).up();
208 211
                 });
@@ -244,16 +247,12 @@ SDP.prototype.toJingle = function (elem, thecreator) {
244 247
                     elem.attrs({name: "cname", value:Math.random().toString(36).substring(7)});
245 248
                     elem.up();
246 249
                     var msid = null;
247
-                    if(mline.media == "audio")
248
-                    {
250
+                    if(mline.media == "audio") {
249 251
                         msid = APP.RTC.localAudio._getId();
250
-                    }
251
-                    else
252
-                    {
252
+                    } else {
253 253
                         msid = APP.RTC.localVideo._getId();
254 254
                     }
255
-                    if(msid != null)
256
-                    {
255
+                    if(msid != null) {
257 256
                         msid = SDPUtil.filter_special_chars(msid);
258 257
                         elem.c('parameter');
259 258
                         elem.attrs({name: "msid", value:msid});
@@ -293,8 +292,8 @@ SDP.prototype.toJingle = function (elem, thecreator) {
293 292
             this.rtcpFbToJingle(i, elem, '*');
294 293
 
295 294
             // XEP-0294
296
-            if (SDPUtil.find_line(this.media[i], 'a=extmap:')) {
297
-                lines = SDPUtil.find_lines(this.media[i], 'a=extmap:');
295
+            lines = SDPUtil.find_lines(this.media[i], 'a=extmap:');
296
+            if (lines.length) {
298 297
                 for (j = 0; j < lines.length; j++) {
299 298
                     tmp = SDPUtil.parse_extmap(lines[j]);
300 299
                     elem.c('rtp-hdrext', { xmlns: 'urn:xmpp:jingle:apps:rtp:rtp-hdrext:0',
@@ -351,24 +350,19 @@ SDP.prototype.transportToJingle = function (mediaindex, elem) {
351 350
     elem.c('transport');
352 351
 
353 352
     // XEP-0343 DTLS/SCTP
354
-    if (SDPUtil.find_line(this.media[mediaindex], 'a=sctpmap:').length)
355
-    {
356
-        sctpmap = SDPUtil.find_line(
357
-            this.media[mediaindex], 'a=sctpmap:', self.session);
358
-        if (sctpmap)
359
-        {
360
-            sctpAttrs = SDPUtil.parse_sctpmap(sctpmap);
361
-            elem.c('sctpmap',
362
-                {
363
-                    xmlns: 'urn:xmpp:jingle:transports:dtls-sctp:1',
364
-                    number: sctpAttrs[0], /* SCTP port */
365
-                    protocol: sctpAttrs[1] /* protocol */
366
-                });
367
-            // Optional stream count attribute
368
-            if (sctpAttrs.length > 2)
369
-                elem.attrs({ streams: sctpAttrs[2]});
370
-            elem.up();
371
-        }
353
+    sctpmap
354
+        = SDPUtil.find_line(this.media[mediaindex], 'a=sctpmap:', self.session);
355
+    if (sctpmap) {
356
+        sctpAttrs = SDPUtil.parse_sctpmap(sctpmap);
357
+        elem.c('sctpmap', {
358
+                xmlns: 'urn:xmpp:jingle:transports:dtls-sctp:1',
359
+                number: sctpAttrs[0], /* SCTP port */
360
+                protocol: sctpAttrs[1] /* protocol */
361
+            });
362
+        // Optional stream count attribute
363
+        if (sctpAttrs.length > 2)
364
+            elem.attrs({ streams: sctpAttrs[2]});
365
+        elem.up();
372 366
     }
373 367
     // XEP-0320
374 368
     fingerprints = SDPUtil.find_lines(this.media[mediaindex], 'a=fingerprint:', this.session);
@@ -499,11 +493,9 @@ SDP.prototype.jingle2media = function (content) {
499 493
         // estos hack to reject an m-line.
500 494
         tmp.port = '0';
501 495
     }
502
-    if (content.find('>transport>fingerprint').length || desc.find('encryption').length) {
503
-        if (sctp.length)
504
-            tmp.proto = 'DTLS/SCTP';
505
-        else
506
-            tmp.proto = 'RTP/SAVPF';
496
+    if (content.find('>transport>fingerprint').length
497
+            || desc.find('encryption').length) {
498
+        tmp.proto = sctp.length ? 'DTLS/SCTP' : 'RTP/SAVPF';
507 499
     } else {
508 500
         tmp.proto = 'RTP/AVPF';
509 501
     }

+ 5
- 4
modules/xmpp/SDPUtil.js Bestand weergeven

@@ -8,11 +8,12 @@ SDPUtil = {
8 8
     },
9 9
     iceparams: function (mediadesc, sessiondesc) {
10 10
         var data = null;
11
-        if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
12
-            SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
11
+        var ufrag, pwd;
12
+        if ((ufrag = SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc))
13
+                && (pwd = SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))) {
13 14
             data = {
14
-                ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
15
-                pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
15
+                ufrag: SDPUtil.parse_iceufrag(ufrag),
16
+                pwd: SDPUtil.parse_icepwd(pwd)
16 17
             };
17 18
         }
18 19
         return data;

+ 121
- 86
modules/xmpp/moderator.js Bestand weergeven

@@ -246,103 +246,138 @@ Moderator.prototype.parseConfigOptions =  function (resultIq) {
246 246
     logger.info("Sip gateway enabled:  " + this.sipGatewayEnabled);
247 247
 };
248 248
 
249
-// FIXME =  we need to show the fact that we're waiting for the focus
250
-// to the user(or that focus is not available)
249
+// FIXME We need to show the fact that we're waiting for the focus to the user
250
+// (or that the focus is not available)
251
+/**
252
+ * Allocates the conference focus.
253
+ *
254
+ * @param {Function} callback - the function to be called back upon the
255
+ * successful allocation of the conference focus
256
+ */
251 257
 Moderator.prototype.allocateConferenceFocus =  function (callback) {
252 258
     // Try to use focus user JID from the config
253 259
     this.setFocusUserJid(this.xmppService.options.focusUserJid);
254 260
     // Send create conference IQ
255
-    var iq = this.createConferenceIq();
256 261
     var self = this;
257 262
     this.connection.sendIQ(
258
-        iq,
263
+        this.createConferenceIq(),
259 264
         function (result) {
260
-
261
-            // Setup config options
262
-            self.parseConfigOptions(result);
263
-
264
-            if ('true' === $(result).find('conference').attr('ready')) {
265
-                // Reset both timers
266
-                self.getNextTimeout(true);
267
-                self.getNextErrorTimeout(true);
268
-                // Exec callback
269
-                callback();
270
-            } else {
271
-                var waitMs = self.getNextTimeout();
272
-                logger.info("Waiting for the focus... " + waitMs);
273
-                // Reset error timeout
274
-                self.getNextErrorTimeout(true);
275
-                window.setTimeout(
276
-                    function () {
277
-                        self.allocateConferenceFocus(callback);
278
-                    }, waitMs);
279
-            }
265
+            self._allocateConferenceFocusSuccess(result, callback);
280 266
         },
281 267
         function (error) {
282
-            // Invalid session ? remove and try again
283
-            // without session ID to get a new one
284
-            var invalidSession
285
-                = $(error).find('>error>session-invalid').length;
286
-            if (invalidSession) {
287
-                logger.info("Session expired! - removing");
288
-                self.settings.clearSessionId();
289
-            }
290
-            if ($(error).find('>error>graceful-shutdown').length) {
291
-                self.eventEmitter.emit(XMPPEvents.GRACEFUL_SHUTDOWN);
292
-                return;
293
-            }
294
-            // Check for error returned by the reservation system
295
-            var reservationErr = $(error).find('>error>reservation-error');
296
-            if (reservationErr.length) {
297
-                // Trigger error event
298
-                var errorCode = reservationErr.attr('error-code');
299
-                var errorMsg;
300
-                if ($(error).find('>error>text')) {
301
-                    errorMsg = $(error).find('>error>text').text();
302
-                }
303
-                self.eventEmitter.emit(
304
-                    XMPPEvents.RESERVATION_ERROR, errorCode, errorMsg);
305
-                return;
306
-            }
307
-            // Not authorized to create new room
308
-            if ($(error).find('>error>not-authorized').length) {
309
-                logger.warn("Unauthorized to start the conference", error);
310
-                var toDomain
311
-                    = Strophe.getDomainFromJid(error.getAttribute('to'));
312
-                if (toDomain !==
313
-                    self.xmppService.options.hosts.anonymousdomain) {
314
-                    //FIXME:  "is external" should come either from
315
-                    // the focus or config.js
316
-                    self.externalAuthEnabled = true;
317
-                }
318
-                self.eventEmitter.emit(
319
-                    XMPPEvents.AUTHENTICATION_REQUIRED,
320
-                    function () {
321
-                        self.allocateConferenceFocus(
322
-                            callback);
323
-                    });
324
-                return;
325
-            }
326
-            var waitMs = self.getNextErrorTimeout();
327
-            logger.error("Focus error, retry after " + waitMs, error);
328
-            // Show message
329
-            var focusComponent = self.getFocusComponent();
330
-            var retrySec = waitMs / 1000;
331
-            //FIXME:  message is duplicated ?
332
-            // Do not show in case of session invalid
333
-            // which means just a retry
334
-            if (!invalidSession) {
335
-                self.eventEmitter.emit(XMPPEvents.FOCUS_DISCONNECTED,
336
-                    focusComponent, retrySec);
337
-            }
338
-            // Reset response timeout
339
-            self.getNextTimeout(true);
340
-            window.setTimeout(
268
+            self._allocateConferenceFocusError(error);
269
+        });
270
+    // XXX We're pressed for time here because we're beginning a complex and/or
271
+    // lengthy conference-establishment process which supposedly involves
272
+    // multiple RTTs. We don't have the time to wait for Strophe to decide to
273
+    // send our IQ.
274
+    this.connection.flush();
275
+};
276
+
277
+/**
278
+ * Invoked by {@link #allocateConferenceFocus} upon its request receiving an
279
+ * error result.
280
+ *
281
+ * @param error - the error result of the request that
282
+ * {@link #allocateConferenceFocus} sent
283
+ * @param {Function} callback - the function to be called back upon the
284
+ * successful allocation of the conference focus
285
+ */
286
+Moderator.prototype._allocateConferenceFocusError = function (error, callback) {
287
+    var self = this;
288
+
289
+    // If the session is invalid, remove and try again without session ID to get
290
+    // a new one
291
+    var invalidSession = $(error).find('>error>session-invalid').length;
292
+    if (invalidSession) {
293
+        logger.info("Session expired! - removing");
294
+        self.settings.clearSessionId();
295
+    }
296
+    if ($(error).find('>error>graceful-shutdown').length) {
297
+        self.eventEmitter.emit(XMPPEvents.GRACEFUL_SHUTDOWN);
298
+        return;
299
+    }
300
+    // Check for error returned by the reservation system
301
+    var reservationErr = $(error).find('>error>reservation-error');
302
+    if (reservationErr.length) {
303
+        // Trigger error event
304
+        var errorCode = reservationErr.attr('error-code');
305
+        var errorTextNode = $(error).find('>error>text');
306
+        var errorMsg;
307
+        if (errorTextNode) {
308
+            errorMsg = errorTextNode.text();
309
+        }
310
+        self.eventEmitter.emit(
311
+                XMPPEvents.RESERVATION_ERROR, errorCode, errorMsg);
312
+        return;
313
+    }
314
+    // Not authorized to create new room
315
+    if ($(error).find('>error>not-authorized').length) {
316
+        logger.warn("Unauthorized to start the conference", error);
317
+        var toDomain = Strophe.getDomainFromJid(error.getAttribute('to'));
318
+        if (toDomain !== self.xmppService.options.hosts.anonymousdomain) {
319
+            //FIXME "is external" should come either from the focus or config.js
320
+            self.externalAuthEnabled = true;
321
+        }
322
+        self.eventEmitter.emit(
323
+                XMPPEvents.AUTHENTICATION_REQUIRED,
341 324
                 function () {
342 325
                     self.allocateConferenceFocus(callback);
343
-                }, waitMs);
344
-        }
345
-    );
326
+                });
327
+        return;
328
+    }
329
+    var waitMs = self.getNextErrorTimeout();
330
+    logger.error("Focus error, retry after " + waitMs, error);
331
+    // Show message
332
+    var focusComponent = self.getFocusComponent();
333
+    var retrySec = waitMs / 1000;
334
+    //FIXME: message is duplicated ? Do not show in case of session invalid
335
+    // which means just a retry
336
+    if (!invalidSession) {
337
+        self.eventEmitter.emit(
338
+                XMPPEvents.FOCUS_DISCONNECTED, focusComponent, retrySec);
339
+    }
340
+    // Reset response timeout
341
+    self.getNextTimeout(true);
342
+    window.setTimeout(
343
+            function () {
344
+                self.allocateConferenceFocus(callback);
345
+            },
346
+            waitMs);
347
+};
348
+
349
+/**
350
+ * Invoked by {@link #allocateConferenceFocus} upon its request receiving a
351
+ * success (i.e. non-error) result.
352
+ *
353
+ * @param result - the success (i.e. non-error) result of the request that
354
+ * {@link #allocateConferenceFocus} sent
355
+ * @param {Function} callback - the function to be called back upon the
356
+ * successful allocation of the conference focus
357
+ */
358
+Moderator.prototype._allocateConferenceFocusSuccess = function (
359
+        result,
360
+        callback) {
361
+    // Setup config options
362
+    this.parseConfigOptions(result);
363
+
364
+    // Reset the error timeout (because we haven't failed here).
365
+    this.getNextErrorTimeout(true);
366
+    if ('true' === $(result).find('conference').attr('ready')) {
367
+        // Reset the non-error timeout (because we've succeeded here).
368
+        this.getNextTimeout(true);
369
+        // Exec callback
370
+        callback();
371
+    } else {
372
+        var waitMs = this.getNextTimeout();
373
+        logger.info("Waiting for the focus... " + waitMs);
374
+        var self = this;
375
+        window.setTimeout(
376
+                function () {
377
+                    self.allocateConferenceFocus(callback);
378
+                },
379
+                waitMs);
380
+    }
346 381
 };
347 382
 
348 383
 Moderator.prototype.authenticate = function () {

+ 84
- 31
modules/xmpp/recording.js Bestand weergeven

@@ -1,19 +1,26 @@
1 1
 /* global $, $iq, config, connection, focusMucJid, messageHandler,
2 2
    Toolbar, Util, Promise */
3 3
 var XMPPEvents = require("../../service/xmpp/XMPPEvents");
4
+var JitsiRecorderErrors = require("../../JitsiRecorderErrors");
5
+
4 6
 var logger = require("jitsi-meet-logger").getLogger(__filename);
5 7
 
6 8
 function Recording(type, eventEmitter, connection, focusMucJid, jirecon,
7 9
     roomjid) {
8 10
     this.eventEmitter = eventEmitter;
9 11
     this.connection = connection;
10
-    this.state = "off";
12
+    this.state = null;
11 13
     this.focusMucJid = focusMucJid;
12 14
     this.jirecon = jirecon;
13 15
     this.url = null;
14 16
     this.type = type;
15
-    this._isSupported = ((type === Recording.types.JIBRI)
16
-        || (type === Recording.types.JIRECON && !this.jirecon))? false : true;
17
+    this._isSupported
18
+        = ( type === Recording.types.JIRECON && !this.jirecon
19
+            || type === Recording.types.JIBRI && !this._isServiceAvailable)
20
+            ? false : true;
21
+
22
+    this._isServiceAvailable = false;
23
+
17 24
     /**
18 25
      * The ID of the jirecon recording session. Jirecon generates it when we
19 26
      * initially start recording, and it needs to be used in subsequent requests
@@ -29,18 +36,43 @@ Recording.types = {
29 36
     JIBRI: "jibri"
30 37
 };
31 38
 
39
+Recording.status = {
40
+    ON: "on",
41
+    OFF: "off",
42
+    AVAILABLE: "available",
43
+    UNAVAILABLE: "unavailable",
44
+    START: "start",
45
+    STOP: "stop",
46
+    PENDING: "pending"
47
+};
48
+
32 49
 Recording.prototype.handleJibriPresence = function (jibri) {
33 50
     var attributes = jibri.attributes;
34 51
     if(!attributes)
35 52
         return;
36 53
 
37
-    this._isSupported =
38
-        (attributes.status && attributes.status !== "undefined");
39
-    if(this._isSupported) {
40
-        this.url = attributes.url || null;
41
-        this.state = attributes.status || "off";
42
-    }
43
-    this.eventEmitter.emit(XMPPEvents.RECORDING_STATE_CHANGED);
54
+    var newState = attributes.status;
55
+    console.log("handle jibri presence : ", newState);
56
+    var oldIsAvailable = this._isServiceAvailable;
57
+    // The service is available if the statu isn't undefined.
58
+    this._isServiceAvailable =
59
+        (newState && newState !== "undefined");
60
+
61
+    if (newState === "undefined"
62
+        || oldIsAvailable != this._isServiceAvailable
63
+        // If we receive an OFF state without any recording in progress we
64
+        // consider this to be an initial available state.
65
+        || (this.state === Recording.status.AVAILABLE
66
+            && newState === Recording.status.OFF))
67
+        this.state = (newState === "undefined")
68
+                        ? Recording.status.UNAVAILABLE
69
+                        : Recording.status.AVAILABLE;
70
+    else
71
+        this.state = attributes.status;
72
+
73
+    logger.log("Handle Jibri presence: ", this.state);
74
+
75
+    this.eventEmitter.emit(XMPPEvents.RECORDER_STATE_CHANGED, this.state);
44 76
 };
45 77
 
46 78
 Recording.prototype.setRecordingJibri = function (state, callback, errCallback,
@@ -49,21 +81,23 @@ Recording.prototype.setRecordingJibri = function (state, callback, errCallback,
49 81
         errCallback(new Error("Invalid state!"));
50 82
     }
51 83
     options = options || {};
52
-    // FIXME jibri does not accept IQ without 'url' attribute set ?
53 84
 
85
+    // FIXME jibri does not accept IQ without 'url' attribute set ?
54 86
     var iq = $iq({to: this.focusMucJid, type: 'set'})
55 87
         .c('jibri', {
56
-            "xmlns": 'http://jitsi.org/protocol/jibri',
57
-            "action": (state === 'on') ? 'start' : 'stop',
58
-            "streamid": options.streamId,
59
-            "follow-entity": options.followEntity
88
+        "xmlns": 'http://jitsi.org/protocol/jibri',
89
+        "action": (state === Recording.status.ON)
90
+                    ? Recording.status.START
91
+                    : Recording.status.STOP,
92
+        "streamid": options.streamId,
60 93
         }).up();
61 94
 
62
-    logger.log('Set jibri recording: '+state, iq.nodeTree);
63
-    console.log(iq.nodeTree);
95
+    logger.log('Set jibri recording: ' + state, iq.nodeTree);
96
+    logger.log(iq.nodeTree);
64 97
     this.connection.sendIQ(
65 98
         iq,
66 99
         function (result) {
100
+            logger.log("Result", result);
67 101
             callback($(result).find('jibri').attr('state'),
68 102
             $(result).find('jibri').attr('url'));
69 103
         },
@@ -74,20 +108,23 @@ Recording.prototype.setRecordingJibri = function (state, callback, errCallback,
74 108
 };
75 109
 
76 110
 Recording.prototype.setRecordingJirecon =
77
-function (state, callback, errCallback, options) {
111
+    function (state, callback, errCallback, options) {
112
+
78 113
     if (state == this.state){
79 114
         errCallback(new Error("Invalid state!"));
80 115
     }
81 116
 
82 117
     var iq = $iq({to: this.jirecon, type: 'set'})
83 118
         .c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
84
-            action: (state === 'on') ? 'start' : 'stop',
119
+            action: (state === Recording.status.ON)
120
+                ? Recording.status.START
121
+                : Recording.status.STOP,
85 122
             mucjid: this.roomjid});
86 123
     if (state === 'off'){
87 124
         iq.attrs({rid: this.jireconRid});
88 125
     }
89 126
 
90
-    console.log('Start recording');
127
+    logger.log('Start recording');
91 128
     var self = this;
92 129
     this.connection.sendIQ(
93 130
         iq,
@@ -96,10 +133,10 @@ function (state, callback, errCallback, options) {
96 133
             // provisional?
97 134
             self.jireconRid = $(result).find('recording').attr('rid');
98 135
             console.log('Recording ' +
99
-                ((state === 'on') ? 'started' : 'stopped') +
136
+                ((state === Recording.status.ON) ? 'started' : 'stopped') +
100 137
                 '(jirecon)' + result);
101 138
             self.state = state;
102
-            if (state === 'off'){
139
+            if (state === Recording.status.OFF){
103 140
                 self.jireconRid = null;
104 141
             }
105 142
 
@@ -168,30 +205,46 @@ function (state, callback, errCallback, options) {
168 205
 };
169 206
 
170 207
 /**
171
- *Starts/stops the recording
208
+ * Starts/stops the recording.
172 209
  * @param token token for authentication
173 210
  * @param statusChangeHandler {function} receives the new status as argument.
174 211
  */
175 212
 Recording.prototype.toggleRecording = function (options, statusChangeHandler) {
176
-    if ((!options.token && this.type === Recording.types.COLIBRI) ||
177
-        (!options.streamId && this.type === Recording.types.JIBRI)){
178
-        statusChangeHandler("error", new Error("No token passed!"));
213
+    var oldState = this.state;
214
+
215
+    // If the recorder is currently unavailable we throw an error.
216
+    if (oldState === Recording.status.UNAVAILABLE)
217
+        statusChangeHandler("error",
218
+            new Error(JitsiRecorderErrors.RECORDER_UNAVAILABLE));
219
+
220
+    // If we're about to turn ON the recording we need either a streamId or
221
+    // an authentication token depending on the recording type. If we don't
222
+    // have any of those we throw an error.
223
+    if ((oldState === Recording.status.OFF
224
+        || oldState === Recording.status.AVAILABLE)
225
+        && ((!options.token && this.type === Recording.types.COLIBRI) ||
226
+        (!options.streamId && this.type === Recording.types.JIBRI))) {
227
+        statusChangeHandler("error",
228
+            new Error(JitsiRecorderErrors.NO_TOKEN));
179 229
         logger.error("No token passed!");
180 230
         return;
181 231
     }
182 232
 
183
-    var oldState = this.state;
184
-    var newState = (oldState === 'off' || !oldState) ? 'on' : 'off';
233
+    var newState = (oldState === Recording.status.AVAILABLE
234
+                    || oldState === Recording.status.OFF)
235
+                    ? Recording.status.ON
236
+                    : Recording.status.OFF;
237
+
185 238
     var self = this;
239
+    logger.log("Toggle recording (old state, new state): ", oldState, newState);
186 240
     this.setRecording(newState,
187 241
         function (state, url) {
188
-            logger.log("New recording state: ", state);
242
+            // If the state is undefined we're going to wait for presence
243
+            // update.
189 244
             if (state && state !== oldState) {
190 245
                 self.state = state;
191 246
                 self.url = url;
192 247
                 statusChangeHandler(state);
193
-            } else {
194
-                statusChangeHandler("error", new Error("Status not changed!"));
195 248
             }
196 249
         }, function (error) {
197 250
             statusChangeHandler("error", error);

+ 3
- 4
modules/xmpp/strophe.jingle.js Bestand weergeven

@@ -94,8 +94,8 @@ module.exports = function(XMPP, eventEmitter) {
94 94
             // see http://xmpp.org/extensions/xep-0166.html#concepts-session
95 95
             switch (action) {
96 96
                 case 'session-initiate':
97
-                    console.log("(TIME) received session-initiate:\t",
98
-                                window.performance.now());
97
+                    var now = window.performance.now();
98
+                    console.log("(TIME) received session-initiate:\t", now);
99 99
                     var startMuted = $(iq).find('jingle>startmuted');
100 100
                     if (startMuted && startMuted.length > 0) {
101 101
                         var audioMuted = startMuted.attr("audio");
@@ -116,7 +116,7 @@ module.exports = function(XMPP, eventEmitter) {
116 116
                     var jingleOffer = $(iq).find('>jingle');
117 117
                     // FIXME there's no nice way with event to get the reason
118 118
                     // why the call was rejected
119
-                    eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess, jingleOffer);
119
+                    eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess, jingleOffer, now);
120 120
                     if (!sess.active())
121 121
                     {
122 122
                         // Call not accepted
@@ -267,4 +267,3 @@ module.exports = function(XMPP, eventEmitter) {
267 267
         }
268 268
     });
269 269
 };
270
-

+ 98
- 72
modules/xmpp/xmpp.js Bestand weergeven

@@ -11,12 +11,12 @@ var RTC = require("../RTC/RTC");
11 11
 
12 12
 var authenticatedUser = false;
13 13
 
14
-function createConnection(bosh) {
14
+function createConnection(bosh, token) {
15 15
     bosh = bosh || '/http-bind';
16 16
 
17 17
     // Append token as URL param
18
-    if (this.token) {
19
-        bosh += (bosh.indexOf('?') == -1 ? '?' : '&') + 'token=' + this.token;
18
+    if (token) {
19
+        bosh += (bosh.indexOf('?') == -1 ? '?' : '&') + 'token=' + token;
20 20
     }
21 21
 
22 22
     return new Strophe.Connection(bosh);
@@ -33,16 +33,16 @@ function initStrophePlugins(XMPP) {
33 33
     require("./strophe.logger")();
34 34
 }
35 35
 
36
-function XMPP(options) {
36
+function XMPP(options, token) {
37 37
     this.eventEmitter = new EventEmitter();
38 38
     this.connection = null;
39 39
     this.disconnectInProgress = false;
40
-
40
+    this.performanceTimes = {status: []};
41 41
     this.forceMuted = false;
42 42
     this.options = options;
43 43
     initStrophePlugins(this);
44 44
 
45
-    this.connection = createConnection(options.bosh);
45
+    this.connection = createConnection(options.bosh, token);
46 46
 
47 47
     // Setup a disconnect on unload as a way to facilitate API consumers. It
48 48
     // sounds like they would want that. A problem for them though may be if
@@ -54,8 +54,81 @@ function XMPP(options) {
54 54
 
55 55
 XMPP.prototype.getConnection = function () { return this.connection; };
56 56
 
57
+/**
58
+ * Receive connection status changes and handles them.
59
+ * @password {string} the password passed in connect method
60
+ * @status the connection status
61
+ * @msg message
62
+ */
63
+XMPP.prototype.connectionHandler = function (password, status, msg) {
64
+    var now = window.performance.now();
65
+    this.performanceTimes["status"].push(
66
+        {state: Strophe.getStatusString(status),
67
+        time: now});
68
+    logger.log("(TIME) Strophe " + Strophe.getStatusString(status) +
69
+        (msg ? "[" + msg + "]" : "") + ":\t", now);
70
+    if (status === Strophe.Status.CONNECTED ||
71
+        status === Strophe.Status.ATTACHED) {
72
+        if (this.options.useStunTurn) {
73
+            this.connection.jingle.getStunAndTurnCredentials();
74
+        }
75
+
76
+        logger.info("My Jabber ID: " + this.connection.jid);
77
+
78
+        // Schedule ping ?
79
+        var pingJid = this.connection.domain;
80
+        this.connection.ping.hasPingSupport(
81
+            pingJid,
82
+            function (hasPing) {
83
+                if (hasPing)
84
+                    this.connection.ping.startInterval(pingJid);
85
+                else
86
+                    logger.warn("Ping NOT supported by " + pingJid);
87
+            }.bind(this));
88
+
89
+        if (password)
90
+            authenticatedUser = true;
91
+        if (this.connection && this.connection.connected &&
92
+            Strophe.getResourceFromJid(this.connection.jid)) {
93
+            // .connected is true while connecting?
94
+//                this.connection.send($pres());
95
+            this.eventEmitter.emit(
96
+                    JitsiConnectionEvents.CONNECTION_ESTABLISHED,
97
+                    Strophe.getResourceFromJid(this.connection.jid));
98
+        }
99
+    } else if (status === Strophe.Status.CONNFAIL) {
100
+        if (msg === 'x-strophe-bad-non-anon-jid') {
101
+            this.anonymousConnectionFailed = true;
102
+        } else {
103
+            this.connectionFailed = true;
104
+        }
105
+        this.lastErrorMsg = msg;
106
+    } else if (status === Strophe.Status.DISCONNECTED) {
107
+        // Stop ping interval
108
+        this.connection.ping.stopInterval();
109
+        this.disconnectInProgress = false;
110
+        if (this.anonymousConnectionFailed) {
111
+            // prompt user for username and password
112
+            this.eventEmitter.emit(JitsiConnectionEvents.CONNECTION_FAILED,
113
+                JitsiConnectionErrors.PASSWORD_REQUIRED);
114
+        } else if(this.connectionFailed) {
115
+            this.eventEmitter.emit(JitsiConnectionEvents.CONNECTION_FAILED,
116
+                JitsiConnectionErrors.OTHER_ERROR,
117
+                msg ? msg : this.lastErrorMsg);
118
+        } else {
119
+            this.eventEmitter.emit(
120
+                    JitsiConnectionEvents.CONNECTION_DISCONNECTED,
121
+                    msg ? msg : this.lastErrorMsg);
122
+        }
123
+    } else if (status === Strophe.Status.AUTHFAIL) {
124
+        // wrong password or username, prompt user
125
+        this.eventEmitter.emit(JitsiConnectionEvents.CONNECTION_FAILED,
126
+            JitsiConnectionErrors.PASSWORD_REQUIRED);
127
+
128
+    }
129
+}
130
+
57 131
 XMPP.prototype._connect = function (jid, password) {
58
-    var self = this;
59 132
     // connection.connect() starts the connection process.
60 133
     //
61 134
     // As the connection process proceeds, the user supplied callback will
@@ -83,72 +156,25 @@ XMPP.prototype._connect = function (jid, password) {
83 156
     //  Status.DISCONNECTING - The connection is currently being terminated
84 157
     //  Status.ATTACHED - The connection has been attached
85 158
 
86
-    var anonymousConnectionFailed = false;
87
-    var connectionFailed = false;
88
-    var lastErrorMsg;
89
-    this.connection.connect(jid, password, function (status, msg) {
90
-        logger.log("(TIME) Strophe " + Strophe.getStatusString(status) +
91
-            (msg ? "[" + msg + "]" : "") + "\t:" + window.performance.now());
92
-        if (status === Strophe.Status.CONNECTED) {
93
-            if (self.options.useStunTurn) {
94
-                self.connection.jingle.getStunAndTurnCredentials();
95
-            }
96
-
97
-            logger.info("My Jabber ID: " + self.connection.jid);
98
-
99
-            // Schedule ping ?
100
-            var pingJid = self.connection.domain;
101
-            self.connection.ping.hasPingSupport(
102
-                pingJid,
103
-                function (hasPing) {
104
-                    if (hasPing)
105
-                        self.connection.ping.startInterval(pingJid);
106
-                    else
107
-                        logger.warn("Ping NOT supported by " + pingJid);
108
-                }
109
-            );
110
-
111
-            if (password)
112
-                authenticatedUser = true;
113
-            if (self.connection && self.connection.connected &&
114
-                Strophe.getResourceFromJid(self.connection.jid)) {
115
-                // .connected is true while connecting?
116
-//                self.connection.send($pres());
117
-                self.eventEmitter.emit(
118
-                        JitsiConnectionEvents.CONNECTION_ESTABLISHED,
119
-                        Strophe.getResourceFromJid(self.connection.jid));
120
-            }
121
-        } else if (status === Strophe.Status.CONNFAIL) {
122
-            if (msg === 'x-strophe-bad-non-anon-jid') {
123
-                anonymousConnectionFailed = true;
124
-            } else {
125
-                connectionFailed = true;
126
-            }
127
-            lastErrorMsg = msg;
128
-        } else if (status === Strophe.Status.DISCONNECTED) {
129
-            // Stop ping interval
130
-            self.connection.ping.stopInterval();
131
-            self.disconnectInProgress = false;
132
-            if (anonymousConnectionFailed) {
133
-                // prompt user for username and password
134
-                self.eventEmitter.emit(JitsiConnectionEvents.CONNECTION_FAILED,
135
-                    JitsiConnectionErrors.PASSWORD_REQUIRED);
136
-            } else if(connectionFailed) {
137
-                self.eventEmitter.emit(JitsiConnectionEvents.CONNECTION_FAILED,
138
-                    JitsiConnectionErrors.OTHER_ERROR,
139
-                    msg ? msg : lastErrorMsg);
140
-            } else {
141
-                self.eventEmitter.emit(
142
-                        JitsiConnectionEvents.CONNECTION_DISCONNECTED,
143
-                        msg ? msg : lastErrorMsg);
144
-            }
145
-        } else if (status === Strophe.Status.AUTHFAIL) {
146
-            // wrong password or username, prompt user
147
-            self.eventEmitter.emit(JitsiConnectionEvents.CONNECTION_FAILED,
148
-                JitsiConnectionErrors.PASSWORD_REQUIRED);
159
+    this.anonymousConnectionFailed = false;
160
+    this.connectionFailed = false;
161
+    this.lastErrorMsg;
162
+    this.connection.connect(jid, password,
163
+        this.connectionHandler.bind(this, password));
164
+}
149 165
 
150
-        }
151
-    });
166
+/**
167
+ * Attach to existing connection. Can be used for optimizations. For example:
168
+ * if the connection is created on the server we can attach to it and start
169
+ * using it.
170
+ *
171
+ * @param options {object} connecting options - rid, sid, jid and password.
172
+ */
173
+ XMPP.prototype.attach = function (options) {
174
+    var now = this.performanceTimes["attaching"] = window.performance.now();
175
+    logger.log("(TIME) Strophe Attaching\t:" + now);
176
+    this.connection.attach(options.jid, options.sid, parseInt(options.rid,10)+1,
177
+        this.connectionHandler.bind(this, options.password));
152 178
 }
153 179
 
154 180
 XMPP.prototype.connect = function (jid, password) {

+ 4
- 3
package.json Bestand weergeven

@@ -34,15 +34,16 @@
34 34
     "jshint": "^2.8.0",
35 35
     "precommit-hook": "^3.0.0",
36 36
     "exorcist": "*",
37
-    "uglify-js": "2.4.24"
37
+    "uglify-js": "2.4.24",
38
+    "watchify": "^3.7.0"
38 39
   },
39 40
   "scripts": {
40 41
     "install": "npm run browserify && npm run version && npm run uglifyjs",
41 42
 
42 43
     "browserify": "browserify -d JitsiMeetJS.js -s JitsiMeetJS | exorcist lib-jitsi-meet.js.map > lib-jitsi-meet.js ",
43
-    "version": "VERSION=`./get-version.sh` && sed -i'' -e s/{#COMMIT_HASH#}/${VERSION}/g lib-jitsi-meet.js",
44
+    "version": "VERSION=`./get-version.sh` && echo lib-jitsi-meet version is:${VERSION} && sed -i'' -e s/{#COMMIT_HASH#}/${VERSION}/g lib-jitsi-meet.js",
44 45
     "uglifyjs": "uglifyjs -p relative lib-jitsi-meet.js -o lib-jitsi-meet.min.js --source-map lib-jitsi-meet.min.map --in-source-map lib-jitsi-meet.js.map",
45
-
46
+    "watch": "watchify JitsiMeetJS.js -s JitsiMeetJS -o lib-jitsi-meet.js -v",
46 47
     "lint": "jshint .",
47 48
     "validate": "npm ls"
48 49
   },

+ 0
- 7
service/UI/UIEvents.js Bestand weergeven

@@ -1,7 +0,0 @@
1
-var UIEvents = {
2
-    NICKNAME_CHANGED: "UI.nickname_changed",
3
-    SELECTED_ENDPOINT: "UI.selected_endpoint",
4
-    PINNED_ENDPOINT: "UI.pinned_endpoint",
5
-    LARGEVIDEO_INIT: "UI.largevideo_init"
6
-};
7
-module.exports = UIEvents;

+ 1
- 1
service/xmpp/XMPPEvents.js Bestand weergeven

@@ -95,7 +95,7 @@ var XMPPEvents = {
95 95
     /**
96 96
      * Indicates that recording state changed.
97 97
      */
98
-    RECORDING_STATE_CHANGED: "xmpp.recordingStateChanged",
98
+    RECORDER_STATE_CHANGED: "xmpp.recorderStateChanged",
99 99
     // Designates an event indicating that we received statistics from a
100 100
     // participant in the MUC.
101 101
     REMOTE_STATS: "xmpp.remote_stats",

Laden…
Annuleren
Opslaan