浏览代码

Makes it possible to switch video streams during the session. Adds desktop sharing feature for chrome.

master
paweldomas 11 年前
父节点
当前提交
3e34df8730

+ 14
- 0
app.js 查看文件

166
         var sess = connection.jingle.sessions[sid];
166
         var sess = connection.jingle.sessions[sid];
167
         if (data.stream.id === 'mixedmslabel') return;
167
         if (data.stream.id === 'mixedmslabel') return;
168
         videoTracks = data.stream.getVideoTracks();
168
         videoTracks = data.stream.getVideoTracks();
169
+        console.log("waiting..", videoTracks, selector[0]);
169
         if (videoTracks.length === 0 || selector[0].currentTime > 0) {
170
         if (videoTracks.length === 0 || selector[0].currentTime > 0) {
170
             RTC.attachMediaStream(selector, data.stream); // FIXME: why do i have to do this for FF?
171
             RTC.attachMediaStream(selector, data.stream); // FIXME: why do i have to do this for FF?
171
             $(document).trigger('callactive.jingle', [selector, sid]);
172
             $(document).trigger('callactive.jingle', [selector, sid]);
437
     });
438
     });
438
     console.log('new ssrcs', newssrcs);
439
     console.log('new ssrcs', newssrcs);
439
 
440
 
441
+    // Have to clear presence map to get rid of removed streams
442
+    connection.emuc.clearPresenceMedia();
440
     var i = 0;
443
     var i = 0;
441
     Object.keys(newssrcs).forEach(function (mtype) {
444
     Object.keys(newssrcs).forEach(function (mtype) {
442
         i++;
445
         i++;
542
 });
545
 });
543
 
546
 
544
 $(document).bind('presence.muc', function (event, jid, info, pres) {
547
 $(document).bind('presence.muc', function (event, jid, info, pres) {
548
+
549
+    // Remove old ssrcs coming from the jid
550
+    Object.keys(ssrc2jid).forEach(function(ssrc){
551
+       if(ssrc2jid[ssrc] == jid){
552
+           delete ssrc2jid[ssrc];
553
+       }
554
+    });
555
+
545
     $(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
556
     $(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
546
         //console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
557
         //console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
547
         ssrc2jid[ssrc.getAttribute('ssrc')] = jid;
558
         ssrc2jid[ssrc.getAttribute('ssrc')] = jid;
1191
 //        TODO: Enable settings functionality. Need to uncomment the settings button in index.html.
1202
 //        TODO: Enable settings functionality. Need to uncomment the settings button in index.html.
1192
 //        $('#settingsButton').css({visibility:"visible"});
1203
 //        $('#settingsButton').css({visibility:"visible"});
1193
     }
1204
     }
1205
+    if(isDesktopSharingEnabled()) {
1206
+        $('#desktopsharing').css({display:"inline"});
1207
+    }
1194
 }
1208
 }
1195
 
1209
 
1196
 /*
1210
 /*

+ 2
- 1
config.js 查看文件

8
 //  useStunTurn: true, // use XEP-0215 to fetch STUN and TURN server
8
 //  useStunTurn: true, // use XEP-0215 to fetch STUN and TURN server
9
 //  useIPv6: true, // ipv6 support. use at your own risk
9
 //  useIPv6: true, // ipv6 support. use at your own risk
10
     useNicks: false,
10
     useNicks: false,
11
-    bosh: '//lambada.jitsi.net/http-bind' // FIXME: use xep-0156 for that
11
+    bosh: '//lambada.jitsi.net/http-bind', // FIXME: use xep-0156 for that
12
+    chromeDesktopSharing: false // Desktop sharing is disabled by default
12
 };
13
 };

+ 77
- 0
desktopsharing.js 查看文件

1
+/**
2
+ * Indicates that desktop stream is currently in use(for toggle purpose).
3
+ * @type {boolean}
4
+ */
5
+var isUsingScreenStream = false;
6
+/**
7
+ * Indicates that switch stream operation is in progress and prevent from triggering new events.
8
+ * @type {boolean}
9
+ */
10
+var switchInProgress = false;
11
+
12
+/**
13
+ * @returns {boolean} <tt>true</tt> if desktop sharing feature is available and enabled.
14
+ */
15
+function isDesktopSharingEnabled() {
16
+    // Desktop sharing must be enabled in config and works on chrome only.
17
+    // Flag 'chrome://flags/#enable-usermedia-screen-capture' must be enabled.
18
+    return config.chromeDesktopSharing && RTC.browser == 'chrome';
19
+}
20
+
21
+/*
22
+ * Toggles screen sharing.
23
+ */
24
+function toggleScreenSharing() {
25
+    if (!(connection && connection.connected
26
+        && !switchInProgress
27
+        && getConferenceHandler().peerconnection.signalingState == 'stable'
28
+        && getConferenceHandler().peerconnection.iceConnectionState == 'connected')) {
29
+        return;
30
+    }
31
+    switchInProgress = true;
32
+
33
+    // Only the focus is able to set a shared key.
34
+    if(!isUsingScreenStream)
35
+    {
36
+        // Enable screen stream
37
+        getUserMediaWithConstraints(
38
+            ['screen'],
39
+            function(stream){
40
+                isUsingScreenStream = true;
41
+                gotScreenStream(stream);
42
+            },
43
+            getSwitchStreamFailed
44
+        );
45
+    } else {
46
+        // Disable screen stream
47
+        getUserMediaWithConstraints(
48
+            ['video'],
49
+            function(stream) {
50
+                isUsingScreenStream = false;
51
+                gotScreenStream(stream);
52
+            },
53
+            getSwitchStreamFailed, config.resolution || '360'
54
+        );
55
+    }
56
+}
57
+
58
+function getSwitchStreamFailed(error) {
59
+    console.error("Failed to obtain the stream to switch to", error);
60
+    switchInProgress = false;
61
+}
62
+
63
+function gotScreenStream(stream) {
64
+    var oldStream = connection.jingle.localVideo;
65
+
66
+    change_local_video(stream);
67
+
68
+    // FIXME: will block switchInProgress on true value in case of exception
69
+    getConferenceHandler().switchStreams(stream, oldStream, onDesktopStreamEnabled);
70
+}
71
+
72
+function onDesktopStreamEnabled() {
73
+    // Wait a moment before enabling the button
74
+    window.setTimeout(function() {
75
+        switchInProgress = false;
76
+    }, 3000);
77
+}

+ 6
- 0
index.html 查看文件

6
     <script src="libs/strophe/strophe.jingle.bundle.js?v=8"></script>
6
     <script src="libs/strophe/strophe.jingle.bundle.js?v=8"></script>
7
     <script src="libs/strophe/strophe.jingle.js?v=1"></script>
7
     <script src="libs/strophe/strophe.jingle.js?v=1"></script>
8
     <script src="libs/strophe/strophe.jingle.sdp.js?v=1"></script>
8
     <script src="libs/strophe/strophe.jingle.sdp.js?v=1"></script>
9
+    <script src="libs/strophe/strophe.jingle.sdp.util.js?v=1"></script>
9
     <script src="libs/strophe/strophe.jingle.sessionbase.js?v=1"></script>
10
     <script src="libs/strophe/strophe.jingle.sessionbase.js?v=1"></script>
10
     <script src="libs/strophe/strophe.jingle.session.js?v=1"></script>
11
     <script src="libs/strophe/strophe.jingle.session.js?v=1"></script>
11
     <script src="libs/colibri/colibri.focus.js?v=8"></script><!-- colibri focus implementation -->
12
     <script src="libs/colibri/colibri.focus.js?v=8"></script><!-- colibri focus implementation -->
15
     <script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
16
     <script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
16
     <script src="app.js?v=23"></script><!-- application logic -->
17
     <script src="app.js?v=23"></script><!-- application logic -->
17
     <script src="chat.js?v=3"></script><!-- chat logic -->
18
     <script src="chat.js?v=3"></script><!-- chat logic -->
19
+    <script src="desktopsharing.js?v=1"></script><!-- desktop sharing logic -->
18
     <script src="util.js?v=2"></script><!-- utility functions -->
20
     <script src="util.js?v=2"></script><!-- utility functions -->
19
     <script src="etherpad.js?v=5"></script><!-- etherpad plugin -->
21
     <script src="etherpad.js?v=5"></script><!-- etherpad plugin -->
20
     <script src="smileys.js?v=1"></script><!-- smiley images -->
22
     <script src="smileys.js?v=1"></script><!-- smiley images -->
54
                 <a class="button" onclick='Etherpad.toggleEtherpad(0);'><i title="Open shared document" class="fa fa-file-text fa-lg"></i></a>
56
                 <a class="button" onclick='Etherpad.toggleEtherpad(0);'><i title="Open shared document" class="fa fa-file-text fa-lg"></i></a>
55
             </span>
57
             </span>
56
             <div class="header_button_separator"></div>
58
             <div class="header_button_separator"></div>
59
+            <span id="desktopsharing" style="display: none">
60
+                <a class="button" onclick="toggleScreenSharing();"><i title="Share screen" class="fa fa-desktop fa-lg"></i></a>
61
+                <div class="header_button_separator"></div>
62
+            </span>
57
             <a class="button" onclick='toggleFullScreen();'><i title="Enter / Exit Full Screen" class="fa fa-arrows-alt fa-lg"></i></a>
63
             <a class="button" onclick='toggleFullScreen();'><i title="Enter / Exit Full Screen" class="fa fa-arrows-alt fa-lg"></i></a>
58
         </span>
64
         </span>
59
     </div>
65
     </div>

+ 71
- 53
libs/colibri/colibri.focus.js 查看文件

38
 ColibriFocus.prototype = Object.create(SessionBase.prototype);
38
 ColibriFocus.prototype = Object.create(SessionBase.prototype);
39
 function ColibriFocus(connection, bridgejid) {
39
 function ColibriFocus(connection, bridgejid) {
40
 
40
 
41
-    SessionBase.call(this, connection);
41
+    SessionBase.call(this, connection, Math.random().toString(36).substr(2, 12));
42
 
42
 
43
     this.bridgejid = bridgejid;
43
     this.bridgejid = bridgejid;
44
     this.peers = [];
44
     this.peers = [];
47
     // media types of the conference
47
     // media types of the conference
48
     this.media = ['audio', 'video'];
48
     this.media = ['audio', 'video'];
49
 
49
 
50
-    this.sid = Math.random().toString(36).substr(2, 12);
51
     this.connection.jingle.sessions[this.sid] = this;
50
     this.connection.jingle.sessions[this.sid] = this;
52
     this.mychannel = [];
51
     this.mychannel = [];
53
     this.channels = [];
52
     this.channels = [];
556
 
555
 
557
 // tell everyone about a new participants a=ssrc lines (isadd is true)
556
 // tell everyone about a new participants a=ssrc lines (isadd is true)
558
 // or a leaving participants a=ssrc lines
557
 // or a leaving participants a=ssrc lines
559
-// FIXME: should not take an SDP, but rather the a=ssrc lines and probably a=mid
560
-ColibriFocus.prototype.sendSSRCUpdate = function (sdp, jid, isadd) {
558
+ColibriFocus.prototype.sendSSRCUpdate = function (sdpMediaSsrcs, fromJid, isadd) {
561
     var self = this;
559
     var self = this;
562
     this.peers.forEach(function (peerjid) {
560
     this.peers.forEach(function (peerjid) {
563
-        if (peerjid == jid) return;
564
-        console.log('tell', peerjid, 'about ' + (isadd ? 'new' : 'removed') + ' ssrcs from', jid);
561
+        if (peerjid == fromJid) return;
562
+        console.log('tell', peerjid, 'about ' + (isadd ? 'new' : 'removed') + ' ssrcs from', fromJid);
565
         if (!self.remotessrc[peerjid]) {
563
         if (!self.remotessrc[peerjid]) {
566
             // FIXME: this should only send to participants that are stable, i.e. who have sent a session-accept
564
             // FIXME: this should only send to participants that are stable, i.e. who have sent a session-accept
567
             // possibly, this.remoteSSRC[session.peerjid] does not exist yet
565
             // possibly, this.remoteSSRC[session.peerjid] does not exist yet
568
             console.warn('do we really want to bother', peerjid, 'with updates yet?');
566
             console.warn('do we really want to bother', peerjid, 'with updates yet?');
569
         }
567
         }
570
-        var channel;
571
         var peersess = self.connection.jingle.jid2session[peerjid];
568
         var peersess = self.connection.jingle.jid2session[peerjid];
572
-        var modify = $iq({to: peerjid, type: 'set'})
573
-            .c('jingle', {
574
-                xmlns: 'urn:xmpp:jingle:1',
575
-                action: isadd ? 'addsource' : 'removesource',
576
-                initiator: peersess.initiator,
577
-                sid: peersess.sid
578
-            }
579
-        );
580
-        // FIXME: only announce video ssrcs since we mix audio and dont need
581
-        //      the audio ssrcs therefore
582
-        var modified = false;
583
-        for (channel = 0; channel < sdp.media.length; channel++) {
584
-            modified = true;
585
-            tmp = SDPUtil.find_lines(sdp.media[channel], 'a=ssrc:');
586
-            modify.c('content', {name: SDPUtil.parse_mid(SDPUtil.find_line(sdp.media[channel], 'a=mid:'))});
587
-            modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
588
-            // FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
589
-            tmp.forEach(function (line) {
590
-                var idx = line.indexOf(' ');
591
-                var linessrc = line.substr(0, idx).substr(7);
592
-                modify.attrs({ssrc: linessrc});
593
-
594
-                var kv = line.substr(idx + 1);
595
-                modify.c('parameter');
596
-                if (kv.indexOf(':') == -1) {
597
-                    modify.attrs({ name: kv });
598
-                } else {
599
-                    modify.attrs({ name: kv.split(':', 2)[0] });
600
-                    modify.attrs({ value: kv.split(':', 2)[1] });
601
-                }
602
-                modify.up();
603
-            });
604
-            modify.up(); // end of source
605
-            modify.up(); // end of content
569
+        if(!peersess){
570
+            console.warn('no session with peer: '+peerjid+' yet...');
571
+            return;
606
         }
572
         }
607
-        if (modified) {
608
-            self.connection.sendIQ(modify,
609
-                function (res) {
610
-                    console.warn('got modify result');
611
-                },
612
-                function (err) {
613
-                    console.warn('got modify error', err);
614
-                }
615
-            );
573
+
574
+        self.sendSSRCUpdateIq(sdpMediaSsrcs, peersess.sid, peersess.initiator, peerjid, isadd);
575
+    });
576
+};
577
+
578
+/**
579
+ * Overrides SessionBase.addSource.
580
+ *
581
+ * @param elem proprietary 'add source' Jingle request(XML node).
582
+ * @param fromJid JID of the participant to whom new ssrcs belong.
583
+ */
584
+ColibriFocus.prototype.addSource = function (elem, fromJid) {
585
+
586
+    var self = this;
587
+    this.peerconnection.addSource(elem);
588
+
589
+    var peerSsrc = this.remotessrc[fromJid];
590
+    //console.log("On ADD", self.addssrc, peerSsrc);
591
+    this.peerconnection.addssrc.forEach(function(val, idx){
592
+        if(!peerSsrc[idx]){
593
+            // add ssrc
594
+            peerSsrc[idx] = val;
616
         } else {
595
         } else {
617
-            console.log('modification not necessary');
596
+            if(peerSsrc[idx].indexOf(val) == -1){
597
+                peerSsrc[idx] = peerSsrc[idx]+val;
598
+            }
599
+        }
600
+    });
601
+
602
+    var oldRemoteSdp = new SDP(this.peerconnection.remoteDescription.sdp);
603
+    this.modifySources(function(){
604
+        // Notify other participants about added ssrc
605
+        var remoteSDP = new SDP(self.peerconnection.remoteDescription.sdp);
606
+        var newSSRCs = oldRemoteSdp.getNewMedia(remoteSDP);
607
+        self.sendSSRCUpdate(newSSRCs, fromJid, true);
608
+    });
609
+};
610
+
611
+/**
612
+ * Overrides SessionBase.removeSource.
613
+ *
614
+ * @param elem proprietary 'remove source' Jingle request(XML node).
615
+ * @param fromJid JID of the participant to whom removed ssrcs belong.
616
+ */
617
+ColibriFocus.prototype.removeSource = function (elem, fromJid) {
618
+
619
+    var self = this;
620
+    this.peerconnection.removeSource(elem);
621
+
622
+    var peerSsrc = this.remotessrc[fromJid];
623
+    //console.log("On REMOVE", self.removessrc, peerSsrc);
624
+    this.peerconnection.removessrc.forEach(function(val, idx){
625
+        if(peerSsrc[idx]){
626
+            // Remove ssrc
627
+            peerSsrc[idx] = peerSsrc[idx].replace(val, '');
618
         }
628
         }
619
     });
629
     });
630
+
631
+    var oldSDP = new SDP(self.peerconnection.remoteDescription.sdp);
632
+    this.modifySources(function(){
633
+        // Notify other participants about removed ssrc
634
+        var remoteSDP = new SDP(self.peerconnection.remoteDescription.sdp);
635
+        var removedSSRCs = remoteSDP.getNewMedia(oldSDP);
636
+        self.sendSSRCUpdate(removedSSRCs, fromJid, false);
637
+    });
620
 };
638
 };
621
 
639
 
622
 ColibriFocus.prototype.setRemoteDescription = function (session, elem, desctype) {
640
 ColibriFocus.prototype.setRemoteDescription = function (session, elem, desctype) {
630
     this.updateChannel(remoteSDP, participant);
648
     this.updateChannel(remoteSDP, participant);
631
 
649
 
632
     // ACT 2: tell anyone else about the new SSRCs
650
     // ACT 2: tell anyone else about the new SSRCs
633
-    this.sendSSRCUpdate(remoteSDP, session.peerjid, true);
651
+    this.sendSSRCUpdate(remoteSDP.getMediaSsrcMap(), session.peerjid, true);
634
 
652
 
635
     // ACT 3: note the SSRCs
653
     // ACT 3: note the SSRCs
636
     this.remotessrc[session.peerjid] = [];
654
     this.remotessrc[session.peerjid] = [];
792
         sdp.media[j] += ssrcs[j];
810
         sdp.media[j] += ssrcs[j];
793
         this.peerconnection.enqueueRemoveSsrc(j, ssrcs[j]);
811
         this.peerconnection.enqueueRemoveSsrc(j, ssrcs[j]);
794
     }
812
     }
795
-    this.sendSSRCUpdate(sdp, session.peerjid, false);
813
+    this.sendSSRCUpdate(sdp.getMediaSsrcMap(), session.peerjid, false);
796
 
814
 
797
     delete this.remotessrc[session.peerjid];
815
     delete this.remotessrc[session.peerjid];
798
     this.modifySources();
816
     this.modifySources();

+ 8
- 0
libs/colibri/colibri.session.js 查看文件

61
     console.log('ColibriSession.accept');
61
     console.log('ColibriSession.accept');
62
 };
62
 };
63
 
63
 
64
+ColibriSession.prototype.addSource = function (elem, fromJid) {
65
+    this.colibri.addSource(elem, fromJid);
66
+};
67
+
68
+ColibriSession.prototype.removeSource = function (elem, fromJid) {
69
+    this.colibri.removeSource(elem, fromJid);
70
+};
71
+
64
 ColibriSession.prototype.terminate = function (reason) {
72
 ColibriSession.prototype.terminate = function (reason) {
65
     this.colibri.terminate(this, reason);
73
     this.colibri.terminate(this, reason);
66
 };
74
 };

+ 32
- 3
libs/strophe/strophe.jingle.adapter.js 查看文件

25
      */
25
      */
26
     this.pendingop = null;
26
     this.pendingop = null;
27
 
27
 
28
+    /**
29
+     * Flag indicates that peer connection stream have changed and modifySources should proceed.
30
+     * @type {boolean}
31
+     */
32
+    this.switchstreams = false;
33
+
28
     // override as desired
34
     // override as desired
29
     this.trace = function(what, info) {
35
     this.trace = function(what, info) {
30
         //console.warn('WTRACE', what, info);
36
         //console.warn('WTRACE', what, info);
197
     console.log('addssrc', new Date().getTime());
203
     console.log('addssrc', new Date().getTime());
198
     console.log('ice', this.iceConnectionState);
204
     console.log('ice', this.iceConnectionState);
199
     var sdp = new SDP(this.remoteDescription.sdp);
205
     var sdp = new SDP(this.remoteDescription.sdp);
206
+    var mySdp = new SDP(this.peerconnection.localDescription.sdp);
200
 
207
 
201
     var self = this;
208
     var self = this;
202
     $(elem).each(function (idx, content) {
209
     $(elem).each(function (idx, content) {
205
         tmp = $(content).find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
212
         tmp = $(content).find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
206
         tmp.each(function () {
213
         tmp.each(function () {
207
             var ssrc = $(this).attr('ssrc');
214
             var ssrc = $(this).attr('ssrc');
215
+            if(mySdp.containsSSRC(ssrc)){
216
+                /**
217
+                 * This happens when multiple participants change their streams at the same time and
218
+                 * ColibriFocus.modifySources have to wait for stable state. In the meantime multiple
219
+                 * addssrc are scheduled for update IQ. See
220
+                 */
221
+                console.warn("Got add stream request for my own ssrc: "+ssrc);
222
+                return;
223
+            }
208
             $(this).find('>parameter').each(function () {
224
             $(this).find('>parameter').each(function () {
209
                 lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
225
                 lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
210
                 if ($(this).attr('value') && $(this).attr('value').length)
226
                 if ($(this).attr('value') && $(this).attr('value').length)
233
     console.log('removessrc', new Date().getTime());
249
     console.log('removessrc', new Date().getTime());
234
     console.log('ice', this.iceConnectionState);
250
     console.log('ice', this.iceConnectionState);
235
     var sdp = new SDP(this.remoteDescription.sdp);
251
     var sdp = new SDP(this.remoteDescription.sdp);
252
+    var mySdp = new SDP(this.peerconnection.localDescription.sdp);
236
 
253
 
237
     var self = this;
254
     var self = this;
238
     $(elem).each(function (idx, content) {
255
     $(elem).each(function (idx, content) {
241
         tmp = $(content).find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
258
         tmp = $(content).find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
242
         tmp.each(function () {
259
         tmp.each(function () {
243
             var ssrc = $(this).attr('ssrc');
260
             var ssrc = $(this).attr('ssrc');
261
+            // This should never happen, but can be useful for bug detection
262
+            if(mySdp.containsSSRC(ssrc)){
263
+                console.error("Got remove stream request for my own ssrc: "+ssrc);
264
+                return;
265
+            }
244
             $(this).find('>parameter').each(function () {
266
             $(this).find('>parameter').each(function () {
245
                 lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
267
                 lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
246
                 if ($(this).attr('value') && $(this).attr('value').length)
268
                 if ($(this).attr('value') && $(this).attr('value').length)
261
 TraceablePeerConnection.prototype.modifySources = function(successCallback) {
283
 TraceablePeerConnection.prototype.modifySources = function(successCallback) {
262
     var self = this;
284
     var self = this;
263
     if (this.signalingState == 'closed') return;
285
     if (this.signalingState == 'closed') return;
264
-    if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null)){
286
+    if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
287
+        // There is nothing to do since scheduled job might have been executed by another succeeding call
265
         if(successCallback){
288
         if(successCallback){
266
             successCallback();
289
             successCallback();
267
         }
290
         }
282
         return;
305
         return;
283
     }
306
     }
284
 
307
 
308
+    // Reset switch streams flag
309
+    this.switchstreams = false;
310
+
285
     var sdp = new SDP(this.remoteDescription.sdp);
311
     var sdp = new SDP(this.remoteDescription.sdp);
286
 
312
 
287
     // add sources
313
     // add sources
486
     }
512
     }
487
     if (um.indexOf('screen') >= 0) {
513
     if (um.indexOf('screen') >= 0) {
488
         constraints.video = {
514
         constraints.video = {
489
-            "mandatory": {
490
-                "chromeMediaSource": "screen"
515
+            mandatory: {
516
+                chromeMediaSource: "screen",
517
+                maxWidth: window.screen.width,
518
+                maxHeight: window.screen.height,
519
+                maxFrameRate: 3
491
             }
520
             }
492
         };
521
         };
493
     }
522
     }

+ 2
- 2
libs/strophe/strophe.jingle.js 查看文件

141
                 }
141
                 }
142
                 break;
142
                 break;
143
             case 'addsource': // FIXME: proprietary
143
             case 'addsource': // FIXME: proprietary
144
-                sess.addSource($(iq).find('>jingle>content'));
144
+                sess.addSource($(iq).find('>jingle>content'), fromJid);
145
                 break;
145
                 break;
146
             case 'removesource': // FIXME: proprietary
146
             case 'removesource': // FIXME: proprietary
147
-                sess.removeSource($(iq).find('>jingle>content'));
147
+                sess.removeSource($(iq).find('>jingle>content'), fromJid);
148
                 break;
148
                 break;
149
             default:
149
             default:
150
                 console.warn('jingle action not implemented', action);
150
                 console.warn('jingle action not implemented', action);

+ 70
- 315
libs/strophe/strophe.jingle.sdp.js 查看文件

11
     this.session = this.media.shift() + '\r\n';
11
     this.session = this.media.shift() + '\r\n';
12
     this.raw = this.session + this.media.join('');
12
     this.raw = this.session + this.media.join('');
13
 }
13
 }
14
-
14
+/**
15
+ * Returns map of MediaChannel mapped per channel idx.
16
+ */
17
+SDP.prototype.getMediaSsrcMap = function() {
18
+    var self = this;
19
+    var media_ssrcs = {};
20
+    for (channelNum = 0; channelNum < self.media.length; channelNum++) {
21
+        modified = true;
22
+        tmp = SDPUtil.find_lines(self.media[channelNum], 'a=ssrc:');
23
+        var type = SDPUtil.parse_mid(SDPUtil.find_line(self.media[channelNum], 'a=mid:'));
24
+        var channel = new MediaChannel(channelNum, type);
25
+        media_ssrcs[channelNum] = channel;
26
+        tmp.forEach(function (line) {
27
+            var linessrc = line.substring(7).split(' ')[0];
28
+            // allocate new ChannelSsrc
29
+            if(!channel.ssrcs[linessrc]) {
30
+                channel.ssrcs[linessrc] = new ChannelSsrc(linessrc, type);
31
+            }
32
+            channel.ssrcs[linessrc].lines.push(line);
33
+        });
34
+    }
35
+    return media_ssrcs;
36
+}
37
+/**
38
+ * Returns <tt>true</tt> if this SDP contains given SSRC.
39
+ * @param ssrc the ssrc to check.
40
+ * @returns {boolean} <tt>true</tt> if this SDP contains given SSRC.
41
+ */
42
+SDP.prototype.containsSSRC = function(ssrc) {
43
+    var channels = this.getMediaSsrcMap();
44
+    var contains = false;
45
+    Object.keys(channels).forEach(function(chNumber){
46
+        var channel = channels[chNumber];
47
+        //console.log("Check", channel, ssrc);
48
+        if(Object.keys(channel.ssrcs).indexOf(ssrc) != -1){
49
+            contains = true;
50
+        }
51
+    });
52
+    return contains;
53
+}
54
+/**
55
+ * Returns map of MediaChannel that contains only media not contained in <tt>otherSdp</tt>. Mapped by channel idx.
56
+ * @param otherSdp the other SDP to check ssrc with.
57
+ */
58
+SDP.prototype.getNewMedia = function(otherSdp) {
59
+    var myMedia = this.getMediaSsrcMap();
60
+    var othersMedia = otherSdp.getMediaSsrcMap();
61
+    var newMedia = {};
62
+    Object.keys(othersMedia).forEach(function(channelNum) {
63
+        var myChannel = myMedia[channelNum];
64
+        var othersChannel = othersMedia[channelNum];
65
+        if(!myChannel && othersChannel) {
66
+            // Add whole channel
67
+            newMedia[channelNum] = othersChannel;
68
+            return;
69
+        }
70
+        // Look for new ssrcs accross the channel
71
+        Object.keys(othersChannel.ssrcs).forEach(function(ssrc) {
72
+            if(Object.keys(myChannel.ssrcs).indexOf(ssrc) === -1) {
73
+                // Allocate channel if we've found ssrc that doesn't exist in our channel
74
+                if(!newMedia[channelNum]){
75
+                    newMedia[channelNum] = new MediaChannel(othersChannel.chNumber, othersChannel.mediaType);
76
+                }
77
+                newMedia[channelNum].ssrcs[ssrc] = othersChannel.ssrcs[ssrc];
78
+            }
79
+        })
80
+    });
81
+    return newMedia;
82
+}
15
 // remove iSAC and CN from SDP
83
 // remove iSAC and CN from SDP
16
 SDP.prototype.mangle = function () {
84
 SDP.prototype.mangle = function () {
17
     var i, j, mline, lines, rtpmap, newdesc;
85
     var i, j, mline, lines, rtpmap, newdesc;
485
         }
553
         }
486
     }
554
     }
487
     return media;
555
     return media;
488
-};
489
-
490
-SDPUtil = {
491
-    iceparams: function (mediadesc, sessiondesc) {
492
-        var data = null;
493
-        if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
494
-            SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
495
-            data = {
496
-                ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
497
-                pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
498
-            };
499
-        }
500
-        return data;
501
-    },
502
-    parse_iceufrag: function (line) {
503
-        return line.substring(12);
504
-    },
505
-    build_iceufrag: function (frag) {
506
-        return 'a=ice-ufrag:' + frag;
507
-    },
508
-    parse_icepwd: function (line) {
509
-        return line.substring(10);
510
-    },
511
-    build_icepwd: function (pwd) {
512
-        return 'a=ice-pwd:' + pwd;
513
-    },
514
-    parse_mid: function (line) {
515
-        return line.substring(6);
516
-    },
517
-    parse_mline: function (line) {
518
-        var parts = line.substring(2).split(' '),
519
-            data = {};
520
-        data.media = parts.shift();
521
-        data.port = parts.shift();
522
-        data.proto = parts.shift();
523
-        if (parts[parts.length - 1] === '') { // trailing whitespace
524
-            parts.pop();
525
-        }
526
-        data.fmt = parts;
527
-        return data;
528
-    },
529
-    build_mline: function (mline) {
530
-        return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
531
-    },
532
-    parse_rtpmap: function (line) {
533
-        var parts = line.substring(9).split(' '),
534
-            data = {};
535
-        data.id = parts.shift();
536
-        parts = parts[0].split('/');
537
-        data.name = parts.shift();
538
-        data.clockrate = parts.shift();
539
-        data.channels = parts.length ? parts.shift() : '1';
540
-        return data;
541
-    },
542
-    build_rtpmap: function (el) {
543
-        var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
544
-        if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
545
-            line += '/' + el.getAttribute('channels');
546
-        }
547
-        return line;
548
-    },
549
-    parse_crypto: function (line) {
550
-        var parts = line.substring(9).split(' '),
551
-            data = {};
552
-        data.tag = parts.shift();
553
-        data['crypto-suite'] = parts.shift();
554
-        data['key-params'] = parts.shift();
555
-        if (parts.length) {
556
-            data['session-params'] = parts.join(' ');
557
-        }
558
-        return data;
559
-    },
560
-    parse_fingerprint: function (line) { // RFC 4572
561
-        var parts = line.substring(14).split(' '),
562
-            data = {};
563
-        data.hash = parts.shift();
564
-        data.fingerprint = parts.shift();
565
-        // TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
566
-        return data;
567
-    },
568
-    parse_fmtp: function (line) {
569
-        var parts = line.split(' '),
570
-            i, key, value,
571
-            data = [];
572
-        parts.shift();
573
-        parts = parts.join(' ').split(';');
574
-        for (i = 0; i < parts.length; i++) {
575
-            key = parts[i].split('=')[0];
576
-            while (key.length && key[0] == ' ') {
577
-                key = key.substring(1);
578
-            }
579
-            value = parts[i].split('=')[1];
580
-            if (key && value) {
581
-                data.push({name: key, value: value});
582
-            } else if (key) {
583
-                // rfc 4733 (DTMF) style stuff
584
-                data.push({name: '', value: key});
585
-            }
586
-        }
587
-        return data;
588
-    },
589
-    parse_icecandidate: function (line) {
590
-        var candidate = {},
591
-            elems = line.split(' ');
592
-        candidate.foundation = elems[0].substring(12);
593
-        candidate.component = elems[1];
594
-        candidate.protocol = elems[2].toLowerCase();
595
-        candidate.priority = elems[3];
596
-        candidate.ip = elems[4];
597
-        candidate.port = elems[5];
598
-        // elems[6] => "typ"
599
-        candidate.type = elems[7];
600
-        candidate.generation = 0; // default value, may be overwritten below
601
-        for (var i = 8; i < elems.length; i += 2) {
602
-            switch (elems[i]) {
603
-                case 'raddr':
604
-                    candidate['rel-addr'] = elems[i + 1];
605
-                    break;
606
-                case 'rport':
607
-                    candidate['rel-port'] = elems[i + 1];
608
-                    break;
609
-                case 'generation':
610
-                    candidate.generation = elems[i + 1];
611
-                    break;
612
-                default: // TODO
613
-                    console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
614
-            }
615
-        }
616
-        candidate.network = '1';
617
-        candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
618
-        return candidate;
619
-    },
620
-    build_icecandidate: function (cand) {
621
-        var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
622
-        line += ' ';
623
-        switch (cand.type) {
624
-            case 'srflx':
625
-            case 'prflx':
626
-            case 'relay':
627
-                if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
628
-                    line += 'raddr';
629
-                    line += ' ';
630
-                    line += cand['rel-addr'];
631
-                    line += ' ';
632
-                    line += 'rport';
633
-                    line += ' ';
634
-                    line += cand['rel-port'];
635
-                    line += ' ';
636
-                }
637
-                break;
638
-        }
639
-        line += 'generation';
640
-        line += ' ';
641
-        line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
642
-        return line;
643
-    },
644
-    parse_ssrc: function (desc) {
645
-        // proprietary mapping of a=ssrc lines
646
-        // TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
647
-        // and parse according to that
648
-        var lines = desc.split('\r\n'),
649
-            data = {};
650
-        for (var i = 0; i < lines.length; i++) {
651
-            if (lines[i].substring(0, 7) == 'a=ssrc:') {
652
-                var idx = lines[i].indexOf(' ');
653
-                data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
654
-            }
655
-        }
656
-        return data;
657
-    },
658
-    parse_rtcpfb: function (line) {
659
-        var parts = line.substr(10).split(' ');
660
-        var data = {};
661
-        data.pt = parts.shift();
662
-        data.type = parts.shift();
663
-        data.params = parts;
664
-        return data;
665
-    },
666
-    parse_extmap: function (line) {
667
-        var parts = line.substr(9).split(' ');
668
-        var data = {};
669
-        data.value = parts.shift();
670
-        if (data.value.indexOf('/') != -1) {
671
-            data.direction = data.value.substr(data.value.indexOf('/') + 1);
672
-            data.value = data.value.substr(0, data.value.indexOf('/'));
673
-        } else {
674
-            data.direction = 'both';
675
-        }
676
-        data.uri = parts.shift();
677
-        data.params = parts;
678
-        return data;
679
-    },
680
-    find_line: function (haystack, needle, sessionpart) {
681
-        var lines = haystack.split('\r\n');
682
-        for (var i = 0; i < lines.length; i++) {
683
-            if (lines[i].substring(0, needle.length) == needle) {
684
-                return lines[i];
685
-            }
686
-        }
687
-        if (!sessionpart) {
688
-            return false;
689
-        }
690
-        // search session part
691
-        lines = sessionpart.split('\r\n');
692
-        for (var j = 0; j < lines.length; j++) {
693
-            if (lines[j].substring(0, needle.length) == needle) {
694
-                return lines[j];
695
-            }
696
-        }
697
-        return false;
698
-    },
699
-    find_lines: function (haystack, needle, sessionpart) {
700
-        var lines = haystack.split('\r\n'),
701
-            needles = [];
702
-        for (var i = 0; i < lines.length; i++) {
703
-            if (lines[i].substring(0, needle.length) == needle)
704
-                needles.push(lines[i]);
705
-        }
706
-        if (needles.length || !sessionpart) {
707
-            return needles;
708
-        }
709
-        // search session part
710
-        lines = sessionpart.split('\r\n');
711
-        for (var j = 0; j < lines.length; j++) {
712
-            if (lines[j].substring(0, needle.length) == needle) {
713
-                needles.push(lines[j]);
714
-            }
715
-        }
716
-        return needles;
717
-    },
718
-    candidateToJingle: function (line) {
719
-        // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
720
-        //      <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
721
-        if (line.substring(0, 12) != 'a=candidate:') {
722
-            console.log('parseCandidate called with a line that is not a candidate line');
723
-            console.log(line);
724
-            return null;
725
-        }
726
-        if (line.substring(line.length - 2) == '\r\n') // chomp it
727
-            line = line.substring(0, line.length - 2);
728
-        var candidate = {},
729
-            elems = line.split(' '),
730
-            i;
731
-        if (elems[6] != 'typ') {
732
-            console.log('did not find typ in the right place');
733
-            console.log(line);
734
-            return null;
735
-        }
736
-        candidate.foundation = elems[0].substring(12);
737
-        candidate.component = elems[1];
738
-        candidate.protocol = elems[2].toLowerCase();
739
-        candidate.priority = elems[3];
740
-        candidate.ip = elems[4];
741
-        candidate.port = elems[5];
742
-        // elems[6] => "typ"
743
-        candidate.type = elems[7];
744
-        for (i = 8; i < elems.length; i += 2) {
745
-            switch (elems[i]) {
746
-                case 'raddr':
747
-                    candidate['rel-addr'] = elems[i + 1];
748
-                    break;
749
-                case 'rport':
750
-                    candidate['rel-port'] = elems[i + 1];
751
-                    break;
752
-                case 'generation':
753
-                    candidate.generation = elems[i + 1];
754
-                    break;
755
-                default: // TODO
756
-                    console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
757
-            }
758
-        }
759
-        candidate.network = '1';
760
-        candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
761
-        return candidate;
762
-    },
763
-    candidateFromJingle: function (cand) {
764
-        var line = 'a=candidate:';
765
-        line += cand.getAttribute('foundation');
766
-        line += ' ';
767
-        line += cand.getAttribute('component');
768
-        line += ' ';
769
-        line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
770
-        line += ' ';
771
-        line += cand.getAttribute('priority');
772
-        line += ' ';
773
-        line += cand.getAttribute('ip');
774
-        line += ' ';
775
-        line += cand.getAttribute('port');
776
-        line += ' ';
777
-        line += 'typ';
778
-        line += ' ' + cand.getAttribute('type');
779
-        line += ' ';
780
-        switch (cand.getAttribute('type')) {
781
-            case 'srflx':
782
-            case 'prflx':
783
-            case 'relay':
784
-                if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
785
-                    line += 'raddr';
786
-                    line += ' ';
787
-                    line += cand.getAttribute('rel-addr');
788
-                    line += ' ';
789
-                    line += 'rport';
790
-                    line += ' ';
791
-                    line += cand.getAttribute('rel-port');
792
-                    line += ' ';
793
-                }
794
-                break;
795
-        }
796
-        line += 'generation';
797
-        line += ' ';
798
-        line += cand.getAttribute('generation') || '0';
799
-        return line + '\r\n';
800
-    }
801
-};
556
+};

+ 353
- 0
libs/strophe/strophe.jingle.sdp.util.js 查看文件

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

+ 17
- 2
libs/strophe/strophe.jingle.session.js 查看文件

3
 JingleSession.prototype = Object.create(SessionBase.prototype);
3
 JingleSession.prototype = Object.create(SessionBase.prototype);
4
 function JingleSession(me, sid, connection) {
4
 function JingleSession(me, sid, connection) {
5
 
5
 
6
-    SessionBase.call(this, connection);
6
+    SessionBase.call(this, connection, sid);
7
 
7
 
8
     this.me = me;
8
     this.me = me;
9
-    this.sid = sid;
10
     this.initiator = null;
9
     this.initiator = null;
11
     this.responder = null;
10
     this.responder = null;
12
     this.isInitiator = null;
11
     this.isInitiator = null;
155
     );
154
     );
156
 };
155
 };
157
 
156
 
157
+/**
158
+ * Implements SessionBase.sendSSRCUpdate.
159
+ */
160
+JingleSession.prototype.sendSSRCUpdate = function(sdpMediaSsrcs, fromJid, isadd) {
161
+
162
+    var self = this;
163
+    console.log('tell', self.peerjid, 'about ' + (isadd ? 'new' : 'removed') + ' ssrcs from' + self.me);
164
+
165
+    if (!(this.peerconnection.signalingState == 'stable' && this.peerconnection.iceConnectionState == 'connected')){
166
+        console.log("Too early to send updates");
167
+        return;
168
+    }
169
+
170
+    this.sendSSRCUpdateIq(sdpMediaSsrcs, self.sid, self.initiator, self.peerjid, isadd);
171
+};
172
+
158
 JingleSession.prototype.terminate = function (reason) {
173
 JingleSession.prototype.terminate = function (reason) {
159
     this.state = 'ended';
174
     this.state = 'ended';
160
     this.reason = reason;
175
     this.reason = reason;

+ 138
- 4
libs/strophe/strophe.jingle.sessionbase.js 查看文件

1
 /**
1
 /**
2
  * Base class for ColibriFocus and JingleSession.
2
  * Base class for ColibriFocus and JingleSession.
3
  * @param connection Strophe connection object
3
  * @param connection Strophe connection object
4
+ * @param sid my session identifier(resource)
4
  * @constructor
5
  * @constructor
5
  */
6
  */
6
-function SessionBase(connection){
7
+function SessionBase(connection, sid){
7
 
8
 
8
     this.connection = connection;
9
     this.connection = connection;
10
+    this.sid = sid;
9
     this.peerconnection
11
     this.peerconnection
10
         = new TraceablePeerConnection(
12
         = new TraceablePeerConnection(
11
             connection.jingle.ice_config,
13
             connection.jingle.ice_config,
13
 }
15
 }
14
 
16
 
15
 
17
 
16
-SessionBase.prototype.modifySources = function() {
18
+SessionBase.prototype.modifySources = function (successCallback) {
17
     var self = this;
19
     var self = this;
18
     this.peerconnection.modifySources(function(){
20
     this.peerconnection.modifySources(function(){
19
         $(document).trigger('setLocalDescription.jingle', [self.sid]);
21
         $(document).trigger('setLocalDescription.jingle', [self.sid]);
22
+        if(successCallback) {
23
+            successCallback();
24
+        }
20
     });
25
     });
21
 };
26
 };
22
 
27
 
23
-SessionBase.prototype.addSource = function (elem) {
28
+SessionBase.prototype.addSource = function (elem, fromJid) {
24
 
29
 
25
     this.peerconnection.addSource(elem);
30
     this.peerconnection.addSource(elem);
26
 
31
 
27
     this.modifySources();
32
     this.modifySources();
28
 };
33
 };
29
 
34
 
30
-SessionBase.prototype.removeSource = function (elem) {
35
+SessionBase.prototype.removeSource = function (elem, fromJid) {
31
 
36
 
32
     this.peerconnection.removeSource(elem);
37
     this.peerconnection.removeSource(elem);
33
 
38
 
34
     this.modifySources();
39
     this.modifySources();
35
 };
40
 };
41
+/**
42
+ * Switches video streams.
43
+ * @param new_stream new stream that will be used as video of this session.
44
+ * @param oldStream old video stream of this session.
45
+ * @param success_callback callback executed after successful stream switch.
46
+ */
47
+SessionBase.prototype.switchStreams = function (new_stream, oldStream, success_callback) {
48
+
49
+    var self = this;
50
+
51
+    // Remember SDP to figure out added/removed SSRCs
52
+    var oldSdp = new SDP(self.peerconnection.localDescription.sdp);
53
+
54
+    self.peerconnection.removeStream(oldStream);
55
+
56
+    self.connection.jingle.localVideo = new_stream;
57
+    self.peerconnection.addStream(self.connection.jingle.localVideo);
58
+
59
+    self.connection.jingle.localStreams = [];
60
+    self.connection.jingle.localStreams.push(self.connection.jingle.localAudio);
61
+    self.connection.jingle.localStreams.push(self.connection.jingle.localVideo);
62
+
63
+    self.peerconnection.switchstreams = true;
64
+    self.modifySources(function() {
65
+        console.log('modify sources done');
66
+
67
+        var newSdp = new SDP(self.peerconnection.localDescription.sdp);
68
+        console.log("SDPs", oldSdp, newSdp);
69
+        self.notifyMySSRCUpdate(oldSdp, newSdp);
70
+
71
+        success_callback();
72
+    });
73
+};
74
+
75
+/**
76
+ * Figures out added/removed ssrcs and send update IQs.
77
+ * @param old_sdp SDP object for old description.
78
+ * @param new_sdp SDP object for new description.
79
+ */
80
+SessionBase.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) {
81
+
82
+    var old_media = old_sdp.getMediaSsrcMap();
83
+    var new_media = new_sdp.getMediaSsrcMap();
84
+    //console.log("old/new medias: ", old_media, new_media);
85
+
86
+    var toAdd = old_sdp.getNewMedia(new_sdp);
87
+    var toRemove = new_sdp.getNewMedia(old_sdp);
88
+    //console.log("to add", toAdd);
89
+    //console.log("to remove", toRemove);
90
+    if(Object.keys(toRemove).length > 0){
91
+        this.sendSSRCUpdate(toRemove, null, false);
92
+    }
93
+    if(Object.keys(toAdd).length > 0){
94
+        this.sendSSRCUpdate(toAdd, null, true);
95
+    }
96
+};
97
+
98
+/**
99
+ * Empty method that does nothing by default. It should send SSRC update IQs to session participants.
100
+ * @param sdpMediaSsrcs array of
101
+ * @param fromJid
102
+ * @param isAdd
103
+ */
104
+SessionBase.prototype.sendSSRCUpdate = function(sdpMediaSsrcs, fromJid, isAdd) {
105
+    //FIXME: put default implementation here(maybe from JingleSession?)
106
+}
107
+
108
+/**
109
+ * Sends SSRC update IQ.
110
+ * @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
111
+ * @param sid session identifier that will be put into the IQ.
112
+ * @param initiator initiator identifier.
113
+ * @param toJid destination Jid
114
+ * @param isAdd indicates if this is remove or add operation.
115
+ */
116
+SessionBase.prototype.sendSSRCUpdateIq = function(sdpMediaSsrcs, sid, initiator, toJid, isAdd) {
117
+
118
+    var self = this;
119
+    var modify = $iq({to: toJid, type: 'set'})
120
+        .c('jingle', {
121
+            xmlns: 'urn:xmpp:jingle:1',
122
+            action: isAdd ? 'addsource' : 'removesource',
123
+            initiator: initiator,
124
+            sid: sid
125
+        }
126
+    );
127
+    // FIXME: only announce video ssrcs since we mix audio and dont need
128
+    //      the audio ssrcs therefore
129
+    var modified = false;
130
+    Object.keys(sdpMediaSsrcs).forEach(function(channelNum){
131
+        modified = true;
132
+        var channel = sdpMediaSsrcs[channelNum];
133
+        modify.c('content', {name: channel.mediaType});
134
+        // FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
135
+        // generate sources from lines
136
+        Object.keys(channel.ssrcs).forEach(function(ssrcNum) {
137
+            var mediaSsrc = channel.ssrcs[ssrcNum];
138
+            modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
139
+            modify.attrs({ssrc: mediaSsrc.ssrc});
140
+            // iterate over ssrc lines
141
+            mediaSsrc.lines.forEach(function (line) {
142
+                var idx = line.indexOf(' ');
143
+                var kv = line.substr(idx + 1);
144
+                modify.c('parameter');
145
+                if (kv.indexOf(':') == -1) {
146
+                    modify.attrs({ name: kv });
147
+                } else {
148
+                    modify.attrs({ name: kv.split(':', 2)[0] });
149
+                    modify.attrs({ value: kv.split(':', 2)[1] });
150
+                }
151
+                modify.up(); // end of parameter
152
+            });
153
+            modify.up(); // end of source
154
+        });
155
+        modify.up(); // end of content
156
+    });
157
+    if (modified) {
158
+        self.connection.sendIQ(modify,
159
+            function (res) {
160
+                console.info('got modify result', res);
161
+            },
162
+            function (err) {
163
+                console.error('got modify error', err);
164
+            }
165
+        );
166
+    } else {
167
+        console.log('modification not necessary');
168
+    }
169
+};
36
 
170
 
37
 // SDP-based mute by going recvonly/sendrecv
171
 // SDP-based mute by going recvonly/sendrecv
38
 // FIXME: should probably black out the screen as well
172
 // FIXME: should probably black out the screen as well

+ 8
- 0
muc.js 查看文件

232
         this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
232
         this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
233
         this.presMap['source' + sourceNumber + '_direction'] = direction;
233
         this.presMap['source' + sourceNumber + '_direction'] = direction;
234
     },
234
     },
235
+    clearPresenceMedia: function () {
236
+        var self = this;
237
+        Object.keys(this.presMap).forEach( function(key) {
238
+            if(key.indexOf('source') != -1) {
239
+                delete self.presMap[key];
240
+            }
241
+        });
242
+    },
235
     addPreziToPresence: function (url, currentSlide) {
243
     addPreziToPresence: function (url, currentSlide) {
236
         this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
244
         this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
237
         this.presMap['preziurl'] = url;
245
         this.presMap['preziurl'] = url;

正在加载...
取消
保存