瀏覽代碼

Merge pull request #40 from jitsi/strophe_cleanup

Strophe cleanup
dev1
hristoterezov 9 年之前
父節點
當前提交
f3465b9d01
共有 6 個檔案被更改,包括 267 行新增701 行删除
  1. 22
    3
      JitsiConference.js
  2. 0
    1
      modules/xmpp/ChatRoom.js
  3. 36
    33
      modules/xmpp/JingleSession.js
  4. 155
    585
      modules/xmpp/JingleSessionPC.js
  5. 42
    79
      modules/xmpp/strophe.jingle.js
  6. 12
    0
      service/xmpp/XMPPEvents.js

+ 22
- 3
JitsiConference.js 查看文件

850
  * @param conference the conference
850
  * @param conference the conference
851
  */
851
  */
852
 function setupListeners(conference) {
852
 function setupListeners(conference) {
853
-    conference.xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) {
854
-        conference.rtc.onIncommingCall(event);
855
-        conference.statistics.startRemoteStats(event.peerconnection);
853
+    conference.xmpp.addListener(
854
+        XMPPEvents.CALL_INCOMING, function (jingleSession, jingleOffer) {
855
+
856
+        if (conference.room.isFocus(jingleSession.peerjid)) {
857
+            // Accept incoming call
858
+            conference.room.setJingleSession(jingleSession);
859
+            jingleSession.initialize(false /* initiator */, conference.room);
860
+            conference.rtc.onIncommingCall(jingleSession);
861
+            jingleSession.acceptOffer(jingleOffer, null,
862
+                function (error) {
863
+                    console.error(
864
+                        "Failed to accept incoming Jingle session", error);
865
+                }
866
+            );
867
+            conference.statistics.startRemoteStats(
868
+                    jingleSession.peerconnection);
869
+        } else {
870
+            // Error cause this should never happen unless something is wrong !
871
+            logger.error(
872
+                "Rejecting session-initiate from non focus user: "
873
+                        + jingleSession.peerjid);
874
+        }
856
     });
875
     });
857
 
876
 
858
     conference.room.addListener(XMPPEvents.REMOTE_STREAM_RECEIVED,
877
     conference.room.addListener(XMPPEvents.REMOTE_STREAM_RECEIVED,

+ 0
- 1
modules/xmpp/ChatRoom.js 查看文件

578
 
578
 
579
 ChatRoom.prototype.setJingleSession = function(session){
579
 ChatRoom.prototype.setJingleSession = function(session){
580
     this.session = session;
580
     this.session = session;
581
-    this.session.room = this;
582
 };
581
 };
583
 
582
 
584
 
583
 

+ 36
- 33
modules/xmpp/JingleSession.js 查看文件

5
  */
5
  */
6
 var logger = require("jitsi-meet-logger").getLogger(__filename);
6
 var logger = require("jitsi-meet-logger").getLogger(__filename);
7
 
7
 
8
-function JingleSession(me, sid, connection, service, eventEmitter) {
8
+function JingleSession(me, sid, peerjid, connection,
9
+                       media_constraints, ice_config, service, eventEmitter) {
9
     /**
10
     /**
10
      * Our JID.
11
      * Our JID.
11
      */
12
      */
16
      */
17
      */
17
     this.sid = sid;
18
     this.sid = sid;
18
 
19
 
20
+    /**
21
+     * the JID of the remote peer.
22
+     */
23
+    this.peerjid = peerjid;
24
+
19
     /**
25
     /**
20
      * The XMPP connection.
26
      * The XMPP connection.
21
      */
27
      */
44
     this.drip_container = [];
50
     this.drip_container = [];
45
 
51
 
46
     // Media constraints. Is this WebRTC only?
52
     // Media constraints. Is this WebRTC only?
47
-    this.media_constraints = null;
53
+    this.media_constraints = media_constraints;
48
 
54
 
49
     // ICE servers config (RTCConfiguration?).
55
     // ICE servers config (RTCConfiguration?).
50
-    this.ice_config = {};
56
+    this.ice_config = ice_config;
51
 
57
 
52
     // The chat room instance associated with the session.
58
     // The chat room instance associated with the session.
53
     this.room = null;
59
     this.room = null;
60
+
61
+    // Jingle session state - uninitialized until 'initialize' is called
62
+    this.state = null;
54
 }
63
 }
55
 
64
 
56
 /**
65
 /**
57
  * Prepares this object to initiate a session.
66
  * Prepares this object to initiate a session.
58
- * @param peerjid the JID of the remote peer.
59
  * @param isInitiator whether we will be the Jingle initiator.
67
  * @param isInitiator whether we will be the Jingle initiator.
60
- * @param media_constraints
61
- * @param ice_config
68
+ * @param room <tt>ChatRoom<tt> for the conference associated with this session
62
  */
69
  */
63
-JingleSession.prototype.initialize = function(peerjid, isInitiator,
64
-                                              media_constraints, ice_config) {
65
-    this.media_constraints = media_constraints;
66
-    this.ice_config = ice_config;
67
-
70
+JingleSession.prototype.initialize = function(isInitiator, room) {
68
     if (this.state !== null) {
71
     if (this.state !== null) {
69
         logger.error('attempt to initiate on session ' + this.sid +
72
         logger.error('attempt to initiate on session ' + this.sid +
70
         'in state ' + this.state);
73
         'in state ' + this.state);
71
         return;
74
         return;
72
     }
75
     }
76
+    this.room = room;
73
     this.state = 'pending';
77
     this.state = 'pending';
74
-    this.initiator = isInitiator ? this.me : peerjid;
75
-    this.responder = !isInitiator ? this.me : peerjid;
76
-    this.peerjid = peerjid;
77
-
78
+    this.initiator = isInitiator ? this.me : this.peerjid;
79
+    this.responder = !isInitiator ? this.me : this.peerjid;
78
     this.doInitialize();
80
     this.doInitialize();
79
 };
81
 };
80
 
82
 
89
  */
91
  */
90
 JingleSession.prototype.addIceCandidates = function(contents) {};
92
 JingleSession.prototype.addIceCandidates = function(contents) {};
91
 
93
 
94
+/**
95
+ * Checks if this JingleSession is in 'active' state which means that the call
96
+ * is in progress.
97
+ * @returns {boolean} <tt>true</tt> if this JingleSession is in 'active' state
98
+ *          or <tt>false</tt> otherwise.
99
+ */
100
+JingleSession.prototype.active = function () {
101
+    return this.state === 'active';
102
+};
103
+
92
 /**
104
 /**
93
  * Handles an 'add-source' event.
105
  * Handles an 'add-source' event.
94
  *
106
  *
104
 JingleSession.prototype.removeSources = function(contents) {};
116
 JingleSession.prototype.removeSources = function(contents) {};
105
 
117
 
106
 /**
118
 /**
107
- * Terminates this Jingle session (stops sending media and closes the streams?)
108
- */
109
-JingleSession.prototype.terminate = function() {};
110
-
111
-/**
112
- * Sends a Jingle session-terminate message to the peer and terminates the
113
- * session.
114
- * @param reason
115
- * @param text
119
+ * Terminates this Jingle session by sending session-terminate
120
+ * @param reason XMPP Jingle error condition
121
+ * @param text some meaningful error message
116
  */
122
  */
117
-JingleSession.prototype.sendTerminate = function(reason, text) {};
123
+JingleSession.prototype.terminate = function(reason, text) {};
118
 
124
 
119
 /**
125
 /**
120
  * Handles an offer from the remote peer (prepares to accept a session).
126
  * Handles an offer from the remote peer (prepares to accept a session).
121
  * @param jingle the 'jingle' XML element.
127
  * @param jingle the 'jingle' XML element.
128
+ * @param success callback called when we the incoming session has been accepted
129
+ * @param failure callback called when we fail for any reason, will supply error
130
+ *        object with details(which is meant more to be printed to the logger
131
+ *        than analysed in the code, as the error is unrecoverable anyway)
122
  */
132
  */
123
-JingleSession.prototype.setOffer = function(jingle) {};
124
-
125
-/**
126
- * Handles an answer from the remote peer (prepares to accept a session).
127
- * @param jingle the 'jingle' XML element.
128
- */
129
-JingleSession.prototype.setAnswer = function(jingle) {};
130
-
133
+JingleSession.prototype.acceptOffer = function(jingle, success, failure) {};
131
 
134
 
132
 module.exports = JingleSession;
135
 module.exports = JingleSession;

+ 155
- 585
modules/xmpp/JingleSessionPC.js 查看文件

11
 var RTCBrowserType = require("../RTC/RTCBrowserType");
11
 var RTCBrowserType = require("../RTC/RTCBrowserType");
12
 var RTC = require("../RTC/RTC");
12
 var RTC = require("../RTC/RTC");
13
 
13
 
14
+/**
15
+ * Constant tells how long we're going to wait for IQ response, before timeout
16
+ * error is  triggered.
17
+ * @type {number}
18
+ */
19
+var IQ_TIMEOUT = 10000;
20
+
14
 // Jingle stuff
21
 // Jingle stuff
15
-function JingleSessionPC(me, sid, connection, service) {
16
-    JingleSession.call(this, me, sid, connection, service);
17
-    this.initiator = null;
18
-    this.responder = null;
19
-    this.peerjid = null;
20
-    this.state = null;
22
+function JingleSessionPC(me, sid, peerjid, connection,
23
+                         media_constraints, ice_config, service, eventEmitter) {
24
+    JingleSession.call(this, me, sid, peerjid, connection,
25
+                       media_constraints, ice_config, service, eventEmitter);
21
     this.localSDP = null;
26
     this.localSDP = null;
22
     this.remoteSDP = null;
27
     this.remoteSDP = null;
23
 
28
 
24
-    this.usetrickle = true;
25
-    this.usepranswer = false; // early transport warmup -- mind you, this might fail. depends on webrtc issue 1718
26
-
27
     this.hadstuncandidate = false;
29
     this.hadstuncandidate = false;
28
     this.hadturncandidate = false;
30
     this.hadturncandidate = false;
29
     this.lasticecandidate = false;
31
     this.lasticecandidate = false;
30
 
32
 
31
-    this.statsinterval = null;
32
-
33
-    this.reason = null;
34
-
35
     this.addssrc = [];
33
     this.addssrc = [];
36
     this.removessrc = [];
34
     this.removessrc = [];
37
     this.pendingop = null;
35
     this.pendingop = null;
51
     this.webrtcIceUdpDisable = !!this.service.options.webrtcIceUdpDisable;
49
     this.webrtcIceUdpDisable = !!this.service.options.webrtcIceUdpDisable;
52
     this.webrtcIceTcpDisable = !!this.service.options.webrtcIceTcpDisable;
50
     this.webrtcIceTcpDisable = !!this.service.options.webrtcIceTcpDisable;
53
 
51
 
54
-    /**
55
-     * The indicator which determines whether the (local) video has been muted
56
-     * in response to a user command in contrast to an automatic decision made
57
-     * by the application logic.
58
-     */
59
-    this.videoMuteByUser = false;
60
-
61
     this.modifySourcesQueue = async.queue(this._modifySources.bind(this), 1);
52
     this.modifySourcesQueue = async.queue(this._modifySources.bind(this), 1);
62
     // We start with the queue paused. We resume it when the signaling state is
53
     // We start with the queue paused. We resume it when the signaling state is
63
     // stable and the ice connection state is connected.
54
     // stable and the ice connection state is connected.
68
 JingleSessionPC.prototype.constructor = JingleSessionPC;
59
 JingleSessionPC.prototype.constructor = JingleSessionPC;
69
 
60
 
70
 
61
 
71
-JingleSessionPC.prototype.setOffer = function(offer) {
72
-    this.setRemoteDescription(offer, 'offer');
73
-};
74
-
75
-JingleSessionPC.prototype.setAnswer = function(answer) {
76
-    this.setRemoteDescription(answer, 'answer');
77
-};
78
-
79
 JingleSessionPC.prototype.updateModifySourcesQueue = function() {
62
 JingleSessionPC.prototype.updateModifySourcesQueue = function() {
80
     var signalingState = this.peerconnection.signalingState;
63
     var signalingState = this.peerconnection.signalingState;
81
     var iceConnectionState = this.peerconnection.iceConnectionState;
64
     var iceConnectionState = this.peerconnection.iceConnectionState;
191
                 self.room.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
174
                 self.room.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
192
                 break;
175
                 break;
193
         }
176
         }
194
-        onIceConnectionStateChange(self.sid, self);
195
     };
177
     };
196
     this.peerconnection.onnegotiationneeded = function (event) {
178
     this.peerconnection.onnegotiationneeded = function (event) {
197
         self.room.eventEmitter.emit(XMPPEvents.PEERCONNECTION_READY, self);
179
         self.room.eventEmitter.emit(XMPPEvents.PEERCONNECTION_READY, self);
198
     };
180
     };
199
 };
181
 };
200
 
182
 
201
-function onIceConnectionStateChange(sid, session) {
202
-    switch (session.peerconnection.iceConnectionState) {
203
-        case 'checking':
204
-            session.timeChecking = (new Date()).getTime();
205
-            session.firstconnect = true;
206
-            break;
207
-        case 'completed': // on caller side
208
-        case 'connected':
209
-            if (session.firstconnect) {
210
-                session.firstconnect = false;
211
-                var metadata = {};
212
-                metadata.setupTime
213
-                    = (new Date()).getTime() - session.timeChecking;
214
-                session.peerconnection.getStats(function (res) {
215
-                    if(res && res.result) {
216
-                        res.result().forEach(function (report) {
217
-                            if (report.type == 'googCandidatePair' &&
218
-                                report.stat('googActiveConnection') == 'true') {
219
-                                metadata.localCandidateType
220
-                                    = report.stat('googLocalCandidateType');
221
-                                metadata.remoteCandidateType
222
-                                    = report.stat('googRemoteCandidateType');
223
-
224
-                                // log pair as well so we can get nice pie
225
-                                // charts
226
-                                metadata.candidatePair
227
-                                    = report.stat('googLocalCandidateType') +
228
-                                        ';' +
229
-                                        report.stat('googRemoteCandidateType');
230
-
231
-                                if (report.stat('googRemoteAddress').indexOf('[') === 0)
232
-                                {
233
-                                    metadata.ipv6 = true;
234
-                                }
235
-                            }
236
-                        });
237
-                    }
238
-                });
239
-            }
240
-            break;
241
-    }
242
-}
243
-
244
-JingleSessionPC.prototype.accept = function () {
245
-    this.state = 'active';
246
-
247
-    var pranswer = this.peerconnection.localDescription;
248
-    if (!pranswer || pranswer.type != 'pranswer') {
249
-        return;
250
-    }
251
-    logger.log('going from pranswer to answer');
252
-    if (this.usetrickle) {
253
-        // remove candidates already sent from session-accept
254
-        var lines = SDPUtil.find_lines(pranswer.sdp, 'a=candidate:');
255
-        for (var i = 0; i < lines.length; i++) {
256
-            pranswer.sdp = pranswer.sdp.replace(lines[i] + '\r\n', '');
257
-        }
258
-    }
259
-    while (SDPUtil.find_line(pranswer.sdp, 'a=inactive')) {
260
-        // FIXME: change any inactive to sendrecv or whatever they were originally
261
-        pranswer.sdp = pranswer.sdp.replace('a=inactive', 'a=sendrecv');
262
-    }
263
-    var prsdp = new SDP(pranswer.sdp);
264
-    if (this.webrtcIceTcpDisable) {
265
-        prsdp.removeTcpCandidates = true;
266
-    }
267
-    if (this.webrtcIceUdpDisable) {
268
-        prsdp.removeUdpCandidates = true;
269
-    }
270
-    var accept = $iq({to: this.peerjid,
271
-        type: 'set'})
272
-        .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
273
-            action: 'session-accept',
274
-            initiator: this.initiator,
275
-            responder: this.responder,
276
-            sid: this.sid });
277
-    // FIXME why do we generate session-accept in 3 different places ?
278
-    prsdp.toJingle(
279
-        accept,
280
-        this.initiator == this.me ? 'initiator' : 'responder');
281
-    var sdp = this.peerconnection.localDescription.sdp;
282
-    while (SDPUtil.find_line(sdp, 'a=inactive')) {
283
-        // FIXME: change any inactive to sendrecv or whatever they were originally
284
-        sdp = sdp.replace('a=inactive', 'a=sendrecv');
285
-    }
286
-    var self = this;
287
-    this.peerconnection.setLocalDescription(new RTCSessionDescription({type: 'answer', sdp: sdp}),
288
-        function () {
289
-            self.connection.sendIQ(accept,
290
-                function () {
291
-                    var ack = {};
292
-                    ack.source = 'answer';
293
-                    $(document).trigger('ack.jingle', [self.sid, ack]);
294
-                },
295
-                function (stanza) {
296
-                    var error = ($(stanza).find('error').length) ? {
297
-                        code: $(stanza).find('error').attr('code'),
298
-                        reason: $(stanza).find('error :first')[0].tagName
299
-                    }:{};
300
-                    error.source = 'answer';
301
-                    JingleSessionPC.onJingleError(self.sid, error);
302
-                },
303
-                10000);
304
-        },
305
-        function (e) {
306
-            logger.error('setLocalDescription failed', e);
307
-            self.room.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
308
-        }
309
-    );
310
-};
311
-
312
-JingleSessionPC.prototype.terminate = function (reason) {
313
-    this.state = 'ended';
314
-    this.reason = reason;
315
-    this.peerconnection.close();
316
-    if (this.statsinterval !== null) {
317
-        window.clearInterval(this.statsinterval);
318
-        this.statsinterval = null;
319
-    }
320
-};
321
-
322
-JingleSessionPC.prototype.active = function () {
323
-    return this.state == 'active';
324
-};
325
-
326
 JingleSessionPC.prototype.sendIceCandidate = function (candidate) {
183
 JingleSessionPC.prototype.sendIceCandidate = function (candidate) {
327
     var self = this;
184
     var self = this;
328
     if (candidate && !this.lasticecandidate) {
185
     if (candidate && !this.lasticecandidate) {
340
             this.hadturncandidate = true;
197
             this.hadturncandidate = true;
341
         }
198
         }
342
 
199
 
343
-        if (this.usetrickle) {
344
-            if (this.usedrip) {
345
-                if (this.drip_container.length === 0) {
346
-                    // start 20ms callout
347
-                    window.setTimeout(function () {
348
-                        if (self.drip_container.length === 0) return;
349
-                        self.sendIceCandidates(self.drip_container);
350
-                        self.drip_container = [];
351
-                    }, 20);
352
-
353
-                }
354
-                this.drip_container.push(candidate);
355
-                return;
356
-            } else {
357
-                self.sendIceCandidates([candidate]);
358
-                // FIXME this.lasticecandidate is going to be set to true
359
-                // bellow and that seems wrong. The execution doesn't come here
360
-                // with the default values at the time of this writing.
200
+        if (this.usedrip) {
201
+            if (this.drip_container.length === 0) {
202
+                // start 20ms callout
203
+                window.setTimeout(function () {
204
+                    if (self.drip_container.length === 0) return;
205
+                    self.sendIceCandidates(self.drip_container);
206
+                    self.drip_container = [];
207
+                }, 20);
361
             }
208
             }
209
+            this.drip_container.push(candidate);
210
+        } else {
211
+            self.sendIceCandidates([candidate]);
362
         }
212
         }
363
     } else {
213
     } else {
364
-        //logger.log('sendIceCandidate: last candidate.');
365
-        if (!this.usetrickle) {
366
-            //logger.log('should send full offer now...');
367
-            //FIXME why do we generate session-accept in 3 different places ?
368
-            var init = $iq({to: this.peerjid,
369
-                type: 'set'})
370
-                .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
371
-                    action: this.peerconnection.localDescription.type == 'offer' ? 'session-initiate' : 'session-accept',
372
-                    initiator: this.initiator,
373
-                    sid: this.sid});
374
-            this.localSDP = new SDP(this.peerconnection.localDescription.sdp);
375
-            if (self.webrtcIceTcpDisable) {
376
-                this.localSDP.removeTcpCandidates = true;
377
-            }
378
-            if (self.webrtcIceUdpDisable) {
379
-                this.localSDP.removeUdpCandidates = true;
380
-            }
381
-            var sendJingle = function (ssrc) {
382
-                if(!ssrc)
383
-                    ssrc = {};
384
-                self.localSDP.toJingle(
385
-                    init,
386
-                    self.initiator == self.me ? 'initiator' : 'responder',
387
-                    ssrc);
388
-                self.connection.sendIQ(init,
389
-                    function () {
390
-                        //logger.log('session initiate ack');
391
-                        var ack = {};
392
-                        ack.source = 'offer';
393
-                        $(document).trigger('ack.jingle', [self.sid, ack]);
394
-                    },
395
-                    function (stanza) {
396
-                        self.state = 'error';
397
-                        self.peerconnection.close();
398
-                        var error = ($(stanza).find('error').length) ? {
399
-                            code: $(stanza).find('error').attr('code'),
400
-                            reason: $(stanza).find('error :first')[0].tagName,
401
-                        }:{};
402
-                        error.source = 'offer';
403
-                        JingleSessionPC.onJingleError(self.sid, error);
404
-                    },
405
-                    10000);
406
-            };
407
-            sendJingle();
408
-        }
214
+        logger.log('sendIceCandidate: last candidate.');
215
+        // FIXME: remember to re-think in ICE-restart
409
         this.lasticecandidate = true;
216
         this.lasticecandidate = true;
410
         logger.log('Have we encountered any srflx candidates? ' + this.hadstuncandidate);
217
         logger.log('Have we encountered any srflx candidates? ' + this.hadstuncandidate);
411
         logger.log('Have we encountered any relay candidates? ' + this.hadturncandidate);
218
         logger.log('Have we encountered any relay candidates? ' + this.hadturncandidate);
412
-
413
-        if (!(this.hadstuncandidate || this.hadturncandidate) && this.peerconnection.signalingState != 'closed') {
414
-            $(document).trigger('nostuncandidates.jingle', [this.sid]);
415
-        }
416
     }
219
     }
417
 };
220
 };
418
 
221
 
454
     }
257
     }
455
     // might merge last-candidate notification into this, but it is called alot later. See webrtc issue #2340
258
     // might merge last-candidate notification into this, but it is called alot later. See webrtc issue #2340
456
     //logger.log('was this the last candidate', this.lasticecandidate);
259
     //logger.log('was this the last candidate', this.lasticecandidate);
457
-    this.connection.sendIQ(cand,
458
-        function () {
459
-            var ack = {};
460
-            ack.source = 'transportinfo';
461
-            $(document).trigger('ack.jingle', [this.sid, ack]);
462
-        },
463
-        function (stanza) {
464
-            var error = ($(stanza).find('error').length) ? {
465
-                code: $(stanza).find('error').attr('code'),
466
-                reason: $(stanza).find('error :first')[0].tagName,
467
-            }:{};
468
-            error.source = 'transportinfo';
469
-            JingleSessionPC.onJingleError(this.sid, error);
470
-        },
471
-        10000);
260
+    this.connection.sendIQ(
261
+        cand, null, this.newJingleErrorHandler(cand), IQ_TIMEOUT);
472
 };
262
 };
473
 
263
 
474
 JingleSessionPC.prototype.readSsrcInfo = function (contents) {
264
 JingleSessionPC.prototype.readSsrcInfo = function (contents) {
489
     });
279
     });
490
 };
280
 };
491
 
281
 
492
-JingleSessionPC.prototype.setRemoteDescription = function (elem, desctype) {
282
+JingleSessionPC.prototype.acceptOffer = function(jingleOffer,
283
+                                                 success, failure) {
284
+    this.state = 'active';
285
+    this.setRemoteDescription(jingleOffer, 'offer',
286
+        function() {
287
+            this.sendAnswer(success, failure);
288
+        }.bind(this),
289
+        failure);
290
+};
291
+
292
+JingleSessionPC.prototype.setRemoteDescription = function (elem, desctype,
293
+                                                           success, failure) {
493
     //logger.log('setting remote description... ', desctype);
294
     //logger.log('setting remote description... ', desctype);
494
     this.remoteSDP = new SDP('');
295
     this.remoteSDP = new SDP('');
495
     if (this.webrtcIceTcpDisable) {
296
     if (this.webrtcIceTcpDisable) {
501
 
302
 
502
     this.remoteSDP.fromJingle(elem);
303
     this.remoteSDP.fromJingle(elem);
503
     this.readSsrcInfo($(elem).find(">content"));
304
     this.readSsrcInfo($(elem).find(">content"));
504
-    var pcremotedesc = this.peerconnection.remoteDescription;
505
-    if (pcremotedesc) {
506
-        logger.log('setRemoteDescription when remote description is not null, should be pranswer', pcremotedesc);
507
-        if (pcremotedesc.type == 'pranswer') {
508
-            var pranswer = new SDP(pcremotedesc.sdp);
509
-            for (var i = 0; i < pranswer.media.length; i++) {
510
-                // make sure we have ice ufrag and pwd
511
-                if (!SDPUtil.find_line(this.remoteSDP.media[i], 'a=ice-ufrag:', this.remoteSDP.session)) {
512
-                    var ice_ufrag_line = SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session);
513
-                    if (ice_ufrag_line) {
514
-                        this.remoteSDP.media[i] += ice_ufrag_line + '\r\n';
515
-                    } else {
516
-                        logger.warn('no ice ufrag?');
517
-                    }
518
-                    var ice_pwd_line = SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session);
519
-                    if (ice_pwd_line) {
520
-                        this.remoteSDP.media[i] += ice_pwd_line + '\r\n';
521
-                    } else {
522
-                        logger.warn('no ice pwd?');
523
-                    }
524
-                }
525
-                // copy over candidates
526
-                var lines = SDPUtil.find_lines(pranswer.media[i], 'a=candidate:');
527
-                for (var j = 0; j < lines.length; j++) {
528
-                    this.remoteSDP.media[i] += lines[j] + '\r\n';
529
-                }
530
-            }
531
-            this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');
532
-        }
533
-    }
534
     var remotedesc = new RTCSessionDescription({type: desctype, sdp: this.remoteSDP.raw});
305
     var remotedesc = new RTCSessionDescription({type: desctype, sdp: this.remoteSDP.raw});
535
 
306
 
536
     this.peerconnection.setRemoteDescription(remotedesc,
307
     this.peerconnection.setRemoteDescription(remotedesc,
537
         function () {
308
         function () {
538
             //logger.log('setRemoteDescription success');
309
             //logger.log('setRemoteDescription success');
310
+            if (success) {
311
+                success();
312
+            }
539
         },
313
         },
540
         function (e) {
314
         function (e) {
541
             logger.error('setRemoteDescription error', e);
315
             logger.error('setRemoteDescription error', e);
542
-            JingleSessionPC.onJingleFatalError(self, e);
543
-        }
316
+            if (failure)
317
+                failure(e);
318
+            JingleSessionPC.onJingleFatalError(this, e);
319
+        }.bind(this)
544
     );
320
     );
545
 };
321
 };
546
 
322
 
547
-/**
548
- * Adds remote ICE candidates to this Jingle session.
549
- * @param elem An array of Jingle "content" elements?
550
- */
551
-JingleSessionPC.prototype.addIceCandidate = function (elem) {
552
-    var self = this;
553
-    if (this.peerconnection.signalingState == 'closed') {
554
-        return;
555
-    }
556
-    if (!this.peerconnection.remoteDescription && this.peerconnection.signalingState == 'have-local-offer') {
557
-        logger.log('trickle ice candidate arriving before session accept...');
558
-        // create a PRANSWER for setRemoteDescription
559
-        if (!this.remoteSDP) {
560
-            var cobbled = 'v=0\r\n' +
561
-                'o=- 1923518516 2 IN IP4 0.0.0.0\r\n' +// FIXME
562
-                's=-\r\n' +
563
-                't=0 0\r\n';
564
-            // first, take some things from the local description
565
-            for (var i = 0; i < this.localSDP.media.length; i++) {
566
-                cobbled += SDPUtil.find_line(this.localSDP.media[i], 'm=') + '\r\n';
567
-                cobbled += SDPUtil.find_lines(this.localSDP.media[i], 'a=rtpmap:').join('\r\n') + '\r\n';
568
-                var mid_line = SDPUtil.find_line(this.localSDP.media[i], 'a=mid:');
569
-                if (mid_line) {
570
-                    cobbled += mid_line + '\r\n';
571
-                }
572
-                cobbled += 'a=inactive\r\n';
573
-            }
574
-            this.remoteSDP = new SDP(cobbled);
575
-        }
576
-        // then add things like ice and dtls from remote candidate
577
-        elem.each(function () {
578
-            for (var i = 0; i < self.remoteSDP.media.length; i++) {
579
-                if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
580
-                    self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
581
-                    if (!SDPUtil.find_line(self.remoteSDP.media[i], 'a=ice-ufrag:')) {
582
-                        var tmp = $(this).find('transport');
583
-                        self.remoteSDP.media[i] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n';
584
-                        self.remoteSDP.media[i] += 'a=ice-pwd:' + tmp.attr('pwd') + '\r\n';
585
-                        tmp = $(this).find('transport>fingerprint');
586
-                        if (tmp.length) {
587
-                            self.remoteSDP.media[i] += 'a=fingerprint:' + tmp.attr('hash') + ' ' + tmp.text() + '\r\n';
588
-                        } else {
589
-                            logger.log('no dtls fingerprint (webrtc issue #1718?)');
590
-                            self.remoteSDP.media[i] += 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:BAADBAADBAADBAADBAADBAADBAADBAADBAADBAAD\r\n';
591
-                        }
592
-                        break;
593
-                    }
594
-                }
595
-            }
596
-        });
597
-        this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');
598
-
599
-        // we need a complete SDP with ice-ufrag/ice-pwd in all parts
600
-        // this makes the assumption that the PRANSWER is constructed such that the ice-ufrag is in all mediaparts
601
-        // but it could be in the session part as well. since the code above constructs this sdp this can't happen however
602
-        var iscomplete = this.remoteSDP.media.filter(function (mediapart) {
603
-            return SDPUtil.find_line(mediapart, 'a=ice-ufrag:');
604
-        }).length == this.remoteSDP.media.length;
605
-
606
-        if (iscomplete) {
607
-            logger.log('setting pranswer');
608
-            try {
609
-                this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'pranswer', sdp: this.remoteSDP.raw }),
610
-                    function() {
611
-                    },
612
-                    function(e) {
613
-                        logger.log('setRemoteDescription pranswer failed', e.toString());
614
-                    });
615
-            } catch (e) {
616
-                logger.error('setting pranswer failed', e);
617
-            }
618
-        } else {
619
-            //logger.log('not yet setting pranswer');
620
-        }
621
-    }
622
-    // operate on each content element
623
-    elem.each(function () {
624
-        // would love to deactivate this, but firefox still requires it
625
-        var idx = -1;
626
-        var i;
627
-        for (i = 0; i < self.remoteSDP.media.length; i++) {
628
-            if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
629
-                self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
630
-                idx = i;
631
-                break;
632
-            }
633
-        }
634
-        if (idx == -1) { // fall back to localdescription
635
-            for (i = 0; i < self.localSDP.media.length; i++) {
636
-                if (SDPUtil.find_line(self.localSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
637
-                    self.localSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
638
-                    idx = i;
639
-                    break;
640
-                }
641
-            }
642
-        }
643
-        var name = $(this).attr('name');
644
-        // TODO: check ice-pwd and ice-ufrag?
645
-        $(this).find('transport>candidate').each(function () {
646
-            var line, candidate;
647
-            var protocol = this.getAttribute('protocol');
648
-            protocol =
649
-                (typeof protocol === 'string') ? protocol.toLowerCase() : '';
650
-            if ((self.webrtcIceTcpDisable && protocol == 'tcp') ||
651
-                (self.webrtcIceUdpDisable && protocol == 'udp')) {
652
-                return;
653
-            }
654
-
655
-            line = SDPUtil.candidateFromJingle(this);
656
-            candidate = new RTCIceCandidate({sdpMLineIndex: idx,
657
-                sdpMid: name,
658
-                candidate: line});
659
-            try {
660
-                self.peerconnection.addIceCandidate(candidate);
661
-            } catch (e) {
662
-                logger.error('addIceCandidate failed', e.toString(), line);
663
-                self.room.eventEmitter.emit(XMPPEvents.ADD_ICE_CANDIDATE_FAILED,
664
-                    err, self.peerconnection);
665
-            }
666
-        });
667
-    });
668
-};
669
-
670
-JingleSessionPC.prototype.sendAnswer = function (provisional) {
671
-    //logger.log('createAnswer', provisional);
672
-    var self = this;
323
+JingleSessionPC.prototype.sendAnswer = function (success, failure) {
324
+    //logger.log('createAnswer');
673
     this.peerconnection.createAnswer(
325
     this.peerconnection.createAnswer(
674
         function (sdp) {
326
         function (sdp) {
675
-            self.createdAnswer(sdp, provisional);
676
-        },
677
-        function (e) {
678
-            logger.error('createAnswer failed', e);
679
-            self.room.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
680
-        },
327
+            this.createdAnswer(sdp, success, failure);
328
+        }.bind(this),
329
+        function (error) {
330
+            logger.error("createAnswer failed", error);
331
+            if (failure)
332
+                failure(error);
333
+            this.room.eventEmitter.emit(
334
+                    XMPPEvents.CONFERENCE_SETUP_FAILED, error);
335
+        }.bind(this),
681
         this.media_constraints
336
         this.media_constraints
682
     );
337
     );
683
 };
338
 };
684
 
339
 
685
-JingleSessionPC.prototype.createdAnswer = function (sdp, provisional) {
340
+JingleSessionPC.prototype.createdAnswer = function (sdp, success, failure) {
686
     //logger.log('createAnswer callback');
341
     //logger.log('createAnswer callback');
687
     var self = this;
342
     var self = this;
688
     this.localSDP = new SDP(sdp.sdp);
343
     this.localSDP = new SDP(sdp.sdp);
689
-    //this.localSDP.mangle();
690
-    this.usepranswer = provisional === true;
691
-    if (this.usetrickle) {
692
-        if (this.usepranswer) {
693
-            sdp.type = 'pranswer';
694
-            for (var i = 0; i < this.localSDP.media.length; i++) {
695
-                this.localSDP.media[i] = this.localSDP.media[i].replace('a=sendrecv\r\n', 'a=inactive\r\n');
696
-            }
697
-            this.localSDP.raw = this.localSDP.session + '\r\n' + this.localSDP.media.join('');
698
-        }
699
-    }
700
-    var self = this;
701
     var sendJingle = function (ssrcs) {
344
     var sendJingle = function (ssrcs) {
702
-                // FIXME why do we generate session-accept in 3 different places ?
703
                 var accept = $iq({to: self.peerjid,
345
                 var accept = $iq({to: self.peerjid,
704
                     type: 'set'})
346
                     type: 'set'})
705
                     .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
347
                     .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
719
                     ssrcs);
361
                     ssrcs);
720
                 self.fixJingle(accept);
362
                 self.fixJingle(accept);
721
                 self.connection.sendIQ(accept,
363
                 self.connection.sendIQ(accept,
722
-                    function () {
723
-                        var ack = {};
724
-                        ack.source = 'answer';
725
-                        $(document).trigger('ack.jingle', [self.sid, ack]);
726
-                    },
727
-                    function (stanza) {
728
-                        var error = ($(stanza).find('error').length) ? {
729
-                            code: $(stanza).find('error').attr('code'),
730
-                            reason: $(stanza).find('error :first')[0].tagName,
731
-                        }:{};
732
-                        error.source = 'answer';
733
-                        JingleSessionPC.onJingleError(self.sid, error);
734
-                    },
735
-                    10000);
736
-    }
364
+                    success,
365
+                    self.newJingleErrorHandler(accept, failure),
366
+                    IQ_TIMEOUT);
367
+    };
737
     sdp.sdp = this.localSDP.raw;
368
     sdp.sdp = this.localSDP.raw;
738
     this.peerconnection.setLocalDescription(sdp,
369
     this.peerconnection.setLocalDescription(sdp,
739
         function () {
370
         function () {
740
-
741
             //logger.log('setLocalDescription success');
371
             //logger.log('setLocalDescription success');
742
-            if (self.usetrickle && !self.usepranswer) {
743
-                sendJingle();
744
-            }
372
+            sendJingle(success, failure);
745
         },
373
         },
746
-        function (e) {
747
-            logger.error('setLocalDescription failed', e);
374
+        function (error) {
375
+            logger.error('setLocalDescription failed', error);
376
+            if (failure)
377
+                failure(error);
748
             self.room.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
378
             self.room.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
749
         }
379
         }
750
     );
380
     );
759
     }
389
     }
760
 };
390
 };
761
 
391
 
762
-JingleSessionPC.prototype.sendTerminate = function (reason, text) {
763
-    var self = this,
764
-        term = $iq({to: this.peerjid,
765
-            type: 'set'})
766
-            .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
767
-                action: 'session-terminate',
768
-                initiator: this.initiator,
769
-                sid: this.sid})
770
-            .c('reason')
771
-            .c(reason || 'success');
392
+JingleSessionPC.prototype.terminate = function (reason,  text,
393
+                                                success, failure) {
394
+    var term = $iq({to: this.peerjid,
395
+        type: 'set'})
396
+        .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
397
+            action: 'session-terminate',
398
+            initiator: this.initiator,
399
+            sid: this.sid})
400
+        .c('reason')
401
+        .c(reason || 'success');
772
 
402
 
773
     if (text) {
403
     if (text) {
774
         term.up().c('text').t(text);
404
         term.up().c('text').t(text);
775
     }
405
     }
776
 
406
 
777
-    this.connection.sendIQ(term,
778
-        function () {
779
-            self.peerconnection.close();
780
-            self.peerconnection = null;
781
-            self.terminate();
782
-            var ack = {};
783
-            ack.source = 'terminate';
784
-            $(document).trigger('ack.jingle', [self.sid, ack]);
785
-        },
786
-        function (stanza) {
787
-            var error = ($(stanza).find('error').length) ? {
788
-                code: $(stanza).find('error').attr('code'),
789
-                reason: $(stanza).find('error :first')[0].tagName,
790
-            }:{};
791
-            $(document).trigger('ack.jingle', [self.sid, error]);
792
-        },
793
-        10000);
794
-    if (this.statsinterval !== null) {
795
-        window.clearInterval(this.statsinterval);
796
-        this.statsinterval = null;
797
-    }
407
+    this.connection.sendIQ(
408
+        term, success, this.newJingleErrorHandler(term, failure), IQ_TIMEOUT);
409
+
410
+    // this should result in 'onTerminated' being called by strope.jingle.js
411
+    this.connection.jingle.terminate(this.sid);
412
+};
413
+
414
+JingleSessionPC.prototype.onTerminated = function (reasonCondition,
415
+                                                   reasonText) {
416
+    this.state = 'ended';
417
+
418
+    // Do something with reason and reasonCondition when we start to care
419
+    //this.reasonCondition = reasonCondition;
420
+    //this.reasonText = reasonText;
421
+    logger.info("Session terminated", this, reasonCondition, reasonText);
422
+
423
+    if (this.peerconnection)
424
+        this.peerconnection.close();
798
 };
425
 };
799
 
426
 
800
 /**
427
 /**
1224
 
851
 
1225
     if (removed && remove) {
852
     if (removed && remove) {
1226
         logger.info("Sending source-remove", remove.tree());
853
         logger.info("Sending source-remove", remove.tree());
1227
-        this.connection.sendIQ(remove,
1228
-            function (res) {
1229
-                logger.info('got remove result', res);
1230
-            },
1231
-            function (err) {
1232
-                logger.error('got remove error', err);
1233
-            }
1234
-        );
854
+        this.connection.sendIQ(
855
+            remove, null, this.newJingleErrorHandler(remove), IQ_TIMEOUT);
1235
     } else {
856
     } else {
1236
         logger.log('removal not necessary');
857
         logger.log('removal not necessary');
1237
     }
858
     }
1252
 
873
 
1253
     if (added && add) {
874
     if (added && add) {
1254
         logger.info("Sending source-add", add.tree());
875
         logger.info("Sending source-add", add.tree());
1255
-        this.connection.sendIQ(add,
1256
-            function (res) {
1257
-                logger.info('got add result', res);
1258
-            },
1259
-            function (err) {
1260
-                logger.error('got add error', err);
1261
-            }
1262
-        );
876
+        this.connection.sendIQ(
877
+            add, null, this.newJingleErrorHandler(add), IQ_TIMEOUT);
1263
     } else {
878
     } else {
1264
         logger.log('addition not necessary');
879
         logger.log('addition not necessary');
1265
     }
880
     }
1266
 };
881
 };
1267
 
882
 
1268
-JingleSessionPC.prototype.getStats = function (interval) {
1269
-    var self = this;
1270
-    var recv = {audio: 0, video: 0};
1271
-    var lost = {audio: 0, video: 0};
1272
-    var lastrecv = {audio: 0, video: 0};
1273
-    var lastlost = {audio: 0, video: 0};
1274
-    var loss = {audio: 0, video: 0};
1275
-    var delta = {audio: 0, video: 0};
1276
-    this.statsinterval = window.setInterval(function () {
1277
-        if (self && self.peerconnection && self.peerconnection.getStats) {
1278
-            self.peerconnection.getStats(function (stats) {
1279
-                var results = stats.result();
1280
-                // TODO: there are so much statistics you can get from this..
1281
-                for (var i = 0; i < results.length; ++i) {
1282
-                    if (results[i].type == 'ssrc') {
1283
-                        var packetsrecv = results[i].stat('packetsReceived');
1284
-                        var packetslost = results[i].stat('packetsLost');
1285
-                        if (packetsrecv && packetslost) {
1286
-                            packetsrecv = parseInt(packetsrecv, 10);
1287
-                            packetslost = parseInt(packetslost, 10);
1288
-
1289
-                            if (results[i].stat('googFrameRateReceived')) {
1290
-                                lastlost.video = lost.video;
1291
-                                lastrecv.video = recv.video;
1292
-                                recv.video = packetsrecv;
1293
-                                lost.video = packetslost;
1294
-                            } else {
1295
-                                lastlost.audio = lost.audio;
1296
-                                lastrecv.audio = recv.audio;
1297
-                                recv.audio = packetsrecv;
1298
-                                lost.audio = packetslost;
1299
-                            }
1300
-                        }
1301
-                    }
1302
-                }
1303
-                delta.audio = recv.audio - lastrecv.audio;
1304
-                delta.video = recv.video - lastrecv.video;
1305
-                loss.audio = (delta.audio > 0) ? Math.ceil(100 * (lost.audio - lastlost.audio) / delta.audio) : 0;
1306
-                loss.video = (delta.video > 0) ? Math.ceil(100 * (lost.video - lastlost.video) / delta.video) : 0;
1307
-                $(document).trigger('packetloss.jingle', [self.sid, loss]);
1308
-            });
883
+/**
884
+ * Method returns function(errorResponse) which is a callback to be passed to
885
+ * Strophe connection.sendIQ method. An 'error' structure is created that is
886
+ * passed as 1st argument to given <tt>failureCb</tt>. The format of this
887
+ * structure is as follows:
888
+ * {
889
+ *  code: {XMPP error response code}
890
+ *  reason: {the name of XMPP error reason element or 'timeout' if the request
891
+ *           has timed out within <tt>IQ_TIMEOUT</tt> milliseconds}
892
+ *  source: {request.tree() that provides original request}
893
+ *  session: {JingleSessionPC instance on which the error occurred}
894
+ * }
895
+ * @param request Strophe IQ instance which is the request to be dumped into
896
+ *        the error structure
897
+ * @param failureCb function(error) called when error response was returned or
898
+ *        when a timeout has occurred.
899
+ * @returns {function(this:JingleSessionPC)}
900
+ */
901
+JingleSessionPC.prototype.newJingleErrorHandler = function(request, failureCb) {
902
+    return function (errResponse) {
903
+
904
+        var error = { };
905
+
906
+        // Get XMPP error code and condition(reason)
907
+        var errorElSel = $(errResponse).find('error');
908
+        if (errorElSel.length) {
909
+            error.code = errorElSel.attr('code');
910
+            var errorReasonSel = $(errResponse).find('error :first');
911
+            if (errorReasonSel.length)
912
+                error.reason = errorReasonSel[0].tagName;
1309
         }
913
         }
1310
-    }, interval || 3000);
1311
-    return this.statsinterval;
1312
-};
1313
 
914
 
1314
-JingleSessionPC.onJingleError = function (session, error)
1315
-{
1316
-    logger.error("Jingle error", error);
1317
-}
915
+        if (!errResponse) {
916
+            error.reason = 'timeout';
917
+        }
918
+
919
+        error.source = null;
920
+        if (request && "function" == typeof request.tree) {
921
+            error.source = request.tree();
922
+        }
923
+
924
+        error.session = this;
925
+
926
+        logger.error("Jingle error", error);
927
+        if (failureCb) {
928
+            failureCb(error);
929
+        }
930
+
931
+        this.room.eventEmitter.emit(XMPPEvents.JINGLE_ERROR, error);
932
+
933
+    }.bind(this);
934
+};
1318
 
935
 
1319
 JingleSessionPC.onJingleFatalError = function (session, error)
936
 JingleSessionPC.onJingleFatalError = function (session, error)
1320
 {
937
 {
1321
     this.room.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
938
     this.room.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
1322
     this.room.eventEmitter.emit(XMPPEvents.JINGLE_FATAL_ERROR, session, error);
939
     this.room.eventEmitter.emit(XMPPEvents.JINGLE_FATAL_ERROR, session, error);
1323
-}
1324
-
1325
-// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
1326
-JingleSessionPC.prototype.sendKeyframe = function () {
1327
-    var pc = this.peerconnection;
1328
-    logger.log('sendkeyframe', pc.iceConnectionState);
1329
-    if (pc.iceConnectionState !== 'connected') return; // safe...
1330
-    var self = this;
1331
-    pc.setRemoteDescription(
1332
-        pc.remoteDescription,
1333
-        function () {
1334
-            pc.createAnswer(
1335
-                function (modifiedAnswer) {
1336
-                    pc.setLocalDescription(
1337
-                        modifiedAnswer,
1338
-                        function () {
1339
-                            // noop
1340
-                        },
1341
-                        function (error) {
1342
-                            logger.log('triggerKeyframe setLocalDescription failed', error);
1343
-                            self.room.eventEmitter.emit(XMPPEvents.SET_LOCAL_DESCRIPTION_ERROR);
1344
-                        }
1345
-                    );
1346
-                },
1347
-                function (error) {
1348
-                    logger.log('triggerKeyframe createAnswer failed', error);
1349
-                    self.room.eventEmitter.emit(XMPPEvents.CREATE_ANSWER_ERROR);
1350
-                }
1351
-            );
1352
-        },
1353
-        function (error) {
1354
-            logger.log('triggerKeyframe setRemoteDescription failed', error);
1355
-            eventEmitter.emit(XMPPEvents.SET_REMOTE_DESCRIPTION_ERROR);
1356
-        }
1357
-    );
1358
-}
1359
-
940
+};
1360
 
941
 
1361
 JingleSessionPC.prototype.remoteStreamAdded = function (data, times) {
942
 JingleSessionPC.prototype.remoteStreamAdded = function (data, times) {
1362
     var self = this;
943
     var self = this;
1397
     }
978
     }
1398
 
979
 
1399
     this.room.remoteStreamAdded(data, this.sid, thessrc);
980
     this.room.remoteStreamAdded(data, this.sid, thessrc);
1400
-
1401
-    var isVideo = data.stream.getVideoTracks().length > 0;
1402
-    // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
1403
-    if (isVideo &&
1404
-        data.peerjid && this.peerjid === data.peerjid &&
1405
-        data.stream.getVideoTracks().length === 0 &&
1406
-        RTC.localVideo.getTracks().length > 0) {
1407
-        window.setTimeout(function () {
1408
-            self.sendKeyframe();
1409
-        }, 3000);
1410
-    }
1411
-}
981
+};
1412
 
982
 
1413
 /**
983
 /**
1414
  * Handles remote stream removal.
984
  * Handles remote stream removal.
1422
     } else if (streamId && streamId.indexOf('mixedmslabel') === -1) {
992
     } else if (streamId && streamId.indexOf('mixedmslabel') === -1) {
1423
         this.room.eventEmitter.emit(XMPPEvents.REMOTE_STREAM_REMOVED, streamId);
993
         this.room.eventEmitter.emit(XMPPEvents.REMOTE_STREAM_REMOVED, streamId);
1424
     }
994
     }
1425
-}
995
+};
1426
 
996
 
1427
 /**
997
 /**
1428
  * Returns the ice connection state for the peer connection.
998
  * Returns the ice connection state for the peer connection.
1430
  */
1000
  */
1431
 JingleSessionPC.prototype.getIceConnectionState = function () {
1001
 JingleSessionPC.prototype.getIceConnectionState = function () {
1432
     return this.peerconnection.iceConnectionState;
1002
     return this.peerconnection.iceConnectionState;
1433
-}
1003
+};
1434
 
1004
 
1435
 
1005
 
1436
 /**
1006
 /**
1456
 
1026
 
1457
     var sources = $(jingle.tree()).find(">jingle>content>description>source");
1027
     var sources = $(jingle.tree()).find(">jingle>content>description>source");
1458
     return sources && sources.length > 0;
1028
     return sources && sources.length > 0;
1459
-}
1029
+};
1460
 
1030
 
1461
 /**
1031
 /**
1462
  * Fixes the outgoing jingle packets with action source-add by removing the
1032
  * Fixes the outgoing jingle packets with action source-add by removing the
1519
             });
1089
             });
1520
         });
1090
         });
1521
     }
1091
     }
1522
-}
1092
+};
1523
 
1093
 
1524
 /**
1094
 /**
1525
  * Fixes the outgoing jingle packets with action source-remove by removing the
1095
  * Fixes the outgoing jingle packets with action source-remove by removing the
1574
                 }
1144
                 }
1575
             });
1145
             });
1576
         });
1146
         });
1577
-}
1147
+};
1578
 
1148
 
1579
 /**
1149
 /**
1580
  * Returns the description node related to the passed content type. If the node
1150
  * Returns the description node related to the passed content type. If the node

+ 42
- 79
modules/xmpp/strophe.jingle.js 查看文件

61
             logger.log('on jingle ' + action + ' from ' + fromJid, iq);
61
             logger.log('on jingle ' + action + ' from ' + fromJid, iq);
62
             var sess = this.sessions[sid];
62
             var sess = this.sessions[sid];
63
             if ('session-initiate' != action) {
63
             if ('session-initiate' != action) {
64
-                if (sess === null) {
65
-                    ack.type = 'error';
64
+                if (!sess) {
65
+                    ack.attrs({ type: 'error' });
66
                     ack.c('error', {type: 'cancel'})
66
                     ack.c('error', {type: 'cancel'})
67
                         .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
67
                         .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
68
                         .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
68
                         .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
69
+                    logger.warn('invalid session id', iq);
69
                     this.connection.send(ack);
70
                     this.connection.send(ack);
70
                     return true;
71
                     return true;
71
                 }
72
                 }
72
                 // local jid is not checked
73
                 // local jid is not checked
73
                 if (fromJid != sess.peerjid) {
74
                 if (fromJid != sess.peerjid) {
74
-                    logger.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
75
-                    ack.type = 'error';
75
+                    logger.warn(
76
+                        'jid mismatch for session id', sid, sess.peerjid, iq);
77
+                    ack.attrs({ type: 'error' });
76
                     ack.c('error', {type: 'cancel'})
78
                     ack.c('error', {type: 'cancel'})
77
                         .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
79
                         .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
78
                         .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
80
                         .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
82
             } else if (sess !== undefined) {
84
             } else if (sess !== undefined) {
83
                 // existing session with same session id
85
                 // existing session with same session id
84
                 // this might be out-of-order if the sess.peerjid is the same as from
86
                 // this might be out-of-order if the sess.peerjid is the same as from
85
-                ack.type = 'error';
87
+                ack.attrs({ type: 'error' });
86
                 ack.c('error', {type: 'cancel'})
88
                 ack.c('error', {type: 'cancel'})
87
                     .c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
89
                     .c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
88
-                logger.warn('duplicate session id', sid);
90
+                logger.warn('duplicate session id', sid, iq);
89
                 this.connection.send(ack);
91
                 this.connection.send(ack);
90
                 return true;
92
                 return true;
91
             }
93
             }
92
-            // FIXME: check for a defined action
93
-            this.connection.send(ack);
94
             // see http://xmpp.org/extensions/xep-0166.html#concepts-session
94
             // see http://xmpp.org/extensions/xep-0166.html#concepts-session
95
             switch (action) {
95
             switch (action) {
96
                 case 'session-initiate':
96
                 case 'session-initiate':
104
                                 audioMuted === "true", videoMuted === "true");
104
                                 audioMuted === "true", videoMuted === "true");
105
                     }
105
                     }
106
                     sess = new JingleSession(
106
                     sess = new JingleSession(
107
-                        $(iq).attr('to'), $(iq).find('jingle').attr('sid'),
108
-                        this.connection, XMPP);
109
-                    // configure session
110
-
111
-                    var fromBareJid = Strophe.getBareJidFromJid(fromJid);
112
-                    this.connection.emuc.setJingleSession(fromBareJid, sess);
113
-
114
-                    sess.media_constraints = this.media_constraints;
115
-                    sess.ice_config = this.ice_config;
116
-
117
-                    sess.initialize(fromJid, false);
118
-                    eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
119
-                    // FIXME: setRemoteDescription should only be done when this call is to be accepted
120
-                    sess.setOffer($(iq).find('>jingle'));
107
+                            $(iq).attr('to'), $(iq).find('jingle').attr('sid'),
108
+                            fromJid,
109
+                            this.connection,
110
+                            this.media_constraints,
111
+                            this.ice_config, XMPP);
121
 
112
 
122
                     this.sessions[sess.sid] = sess;
113
                     this.sessions[sess.sid] = sess;
123
                     this.jid2session[sess.peerjid] = sess;
114
                     this.jid2session[sess.peerjid] = sess;
124
 
115
 
125
-                    // the callback should either
126
-                    // .sendAnswer and .accept
127
-                    // or .sendTerminate -- not necessarily synchronous
128
-
129
-                    sess.sendAnswer();
130
-                    sess.accept();
131
-                    break;
132
-                case 'session-accept':
133
-                    sess.setAnswer($(iq).find('>jingle'));
134
-                    sess.accept();
135
-                    break;
136
-                case 'session-terminate':
137
-                    // If this is not the focus sending the terminate, we have
138
-                    // nothing more to do here.
139
-                    if (Object.keys(this.sessions).length < 1
140
-                        || !(this.sessions[Object.keys(this.sessions)[0]]
141
-                            instanceof JingleSession))
116
+                    var jingleOffer = $(iq).find('>jingle');
117
+                    // FIXME there's no nice way with event to get the reason
118
+                    // why the call was rejected
119
+                    eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess, jingleOffer);
120
+                    if (!sess.active())
142
                     {
121
                     {
143
-                        break;
122
+                        // Call not accepted
123
+                        ack.attrs({ type: 'error' });
124
+                        ack.c('error', {type: 'cancel'})
125
+                           .c('bad-request',
126
+                            { xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas' })
127
+                            .up();
128
+                        this.terminate(sess.sid);
144
                     }
129
                     }
130
+                    break;
131
+                case 'session-terminate':
145
                     logger.log('terminating...', sess.sid);
132
                     logger.log('terminating...', sess.sid);
146
-                    sess.terminate();
147
-                    this.terminate(sess.sid);
133
+                    var reasonCondition = null;
134
+                    var reasonText = null;
148
                     if ($(iq).find('>jingle>reason').length) {
135
                     if ($(iq).find('>jingle>reason').length) {
149
-                        $(document).trigger('callterminated.jingle', [
150
-                            sess.sid,
151
-                            sess.peerjid,
152
-                            $(iq).find('>jingle>reason>:first')[0].tagName,
153
-                            $(iq).find('>jingle>reason>text').text()
154
-                        ]);
155
-                    } else {
156
-                        $(document).trigger('callterminated.jingle',
157
-                            [sess.sid, sess.peerjid]);
158
-                    }
159
-                    break;
160
-                case 'transport-info':
161
-                    sess.addIceCandidate($(iq).find('>jingle>content'));
162
-                    break;
163
-                case 'session-info':
164
-                    var affected;
165
-                    if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
166
-                        $(document).trigger('ringing.jingle', [sess.sid]);
167
-                    } else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
168
-                        affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
169
-                        $(document).trigger('mute.jingle', [sess.sid, affected]);
170
-                    } else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
171
-                        affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
172
-                        $(document).trigger('unmute.jingle', [sess.sid, affected]);
136
+                        reasonCondition
137
+                            = $(iq).find('>jingle>reason>:first')[0].tagName;
138
+                        reasonText = $(iq).find('>jingle>reason>text').text();
173
                     }
139
                     }
140
+                    this.terminate(sess.sid, reasonCondition, reasonText);
174
                     break;
141
                     break;
175
                 case 'addsource': // FIXME: proprietary, un-jingleish
142
                 case 'addsource': // FIXME: proprietary, un-jingleish
176
                 case 'source-add': // FIXME: proprietary
143
                 case 'source-add': // FIXME: proprietary
182
                     break;
149
                     break;
183
                 default:
150
                 default:
184
                     logger.warn('jingle action not implemented', action);
151
                     logger.warn('jingle action not implemented', action);
152
+                    ack.attrs({ type: 'error' });
153
+                    ack.c('error', {type: 'cancel'})
154
+                        .c('bad-request',
155
+                            { xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas' })
156
+                        .up();
185
                     break;
157
                     break;
186
             }
158
             }
159
+            this.connection.send(ack);
187
             return true;
160
             return true;
188
         },
161
         },
189
-        terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
190
-            if (sid === null || sid === undefined) {
191
-                for (sid in this.sessions) {
192
-                    if (this.sessions[sid].state != 'ended') {
193
-                        this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
194
-                        this.sessions[sid].terminate();
195
-                    }
196
-                    delete this.jid2session[this.sessions[sid].peerjid];
197
-                    delete this.sessions[sid];
198
-                }
199
-            } else if (this.sessions.hasOwnProperty(sid)) {
162
+        terminate: function (sid, reasonCondition, reasonText) {
163
+            if (this.sessions.hasOwnProperty(sid)) {
200
                 if (this.sessions[sid].state != 'ended') {
164
                 if (this.sessions[sid].state != 'ended') {
201
-                    this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
202
-                    this.sessions[sid].terminate();
165
+                    this.sessions[sid].onTerminated(reasonCondition, reasonText);
203
                 }
166
                 }
204
                 delete this.jid2session[this.sessions[sid].peerjid];
167
                 delete this.jid2session[this.sessions[sid].peerjid];
205
                 delete this.sessions[sid];
168
                 delete this.sessions[sid];

+ 12
- 0
service/xmpp/XMPPEvents.js 查看文件

40
     FOCUS_DISCONNECTED: 'xmpp.focus_disconnected',
40
     FOCUS_DISCONNECTED: 'xmpp.focus_disconnected',
41
     FOCUS_LEFT: "xmpp.focus_left",
41
     FOCUS_LEFT: "xmpp.focus_left",
42
     GRACEFUL_SHUTDOWN: "xmpp.graceful_shutdown",
42
     GRACEFUL_SHUTDOWN: "xmpp.graceful_shutdown",
43
+    /* Event fired when XMPP error is returned to any request, it is meant to be
44
+     * used to report 'signaling' errors to CallStats
45
+     *
46
+     * {
47
+     *   code: {XMPP error code}
48
+     *   reason: {XMPP error condition}
49
+     *   source = request.tree()
50
+     *   session = {JingleSession instance}
51
+     * }
52
+     */
53
+    JINGLE_ERROR: 'xmpp.jingle_error',
54
+    // Event fired when we have failed to set initial offer
43
     JINGLE_FATAL_ERROR: 'xmpp.jingle_fatal_error',
55
     JINGLE_FATAL_ERROR: 'xmpp.jingle_fatal_error',
44
     // Designates an event indicating that we were kicked from the XMPP MUC.
56
     // Designates an event indicating that we were kicked from the XMPP MUC.
45
     KICKED: "xmpp.kicked",
57
     KICKED: "xmpp.kicked",

Loading…
取消
儲存