|
|
@@ -33,6 +33,11 @@ function JingleSession(me, sid, connection) {
|
|
33
|
33
|
|
|
34
|
34
|
this.reason = null;
|
|
35
|
35
|
|
|
|
36
|
+ this.addssrc = [];
|
|
|
37
|
+ this.removessrc = [];
|
|
|
38
|
+ this.pendingop = null;
|
|
|
39
|
+ this.switchstreams = false;
|
|
|
40
|
+
|
|
36
|
41
|
this.wait = true;
|
|
37
|
42
|
this.localStreamsSSRC = null;
|
|
38
|
43
|
|
|
|
@@ -680,8 +685,52 @@ JingleSession.prototype.addSource = function (elem, fromJid) {
|
|
680
|
685
|
return;
|
|
681
|
686
|
}
|
|
682
|
687
|
|
|
683
|
|
- this.peerconnection.addSource(elem);
|
|
684
|
|
-
|
|
|
688
|
+ console.log('addssrc', new Date().getTime());
|
|
|
689
|
+ console.log('ice', this.peerconnection.iceConnectionState);
|
|
|
690
|
+ var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
|
|
|
691
|
+ var mySdp = new SDP(this.peerconnection.localDescription.sdp);
|
|
|
692
|
+
|
|
|
693
|
+ $(elem).each(function (idx, content) {
|
|
|
694
|
+ var name = $(content).attr('name');
|
|
|
695
|
+ var lines = '';
|
|
|
696
|
+ tmp = $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
|
|
|
697
|
+ var semantics = this.getAttribute('semantics');
|
|
|
698
|
+ var ssrcs = $(this).find('>source').map(function () {
|
|
|
699
|
+ return this.getAttribute('ssrc');
|
|
|
700
|
+ }).get();
|
|
|
701
|
+
|
|
|
702
|
+ if (ssrcs.length != 0) {
|
|
|
703
|
+ lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
|
|
|
704
|
+ }
|
|
|
705
|
+ });
|
|
|
706
|
+ tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
|
|
|
707
|
+ tmp.each(function () {
|
|
|
708
|
+ var ssrc = $(this).attr('ssrc');
|
|
|
709
|
+ if(mySdp.containsSSRC(ssrc)){
|
|
|
710
|
+ /**
|
|
|
711
|
+ * This happens when multiple participants change their streams at the same time and
|
|
|
712
|
+ * ColibriFocus.modifySources have to wait for stable state. In the meantime multiple
|
|
|
713
|
+ * addssrc are scheduled for update IQ. See
|
|
|
714
|
+ */
|
|
|
715
|
+ console.warn("Got add stream request for my own ssrc: "+ssrc);
|
|
|
716
|
+ return;
|
|
|
717
|
+ }
|
|
|
718
|
+ $(this).find('>parameter').each(function () {
|
|
|
719
|
+ lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
|
|
|
720
|
+ if ($(this).attr('value') && $(this).attr('value').length)
|
|
|
721
|
+ lines += ':' + $(this).attr('value');
|
|
|
722
|
+ lines += '\r\n';
|
|
|
723
|
+ });
|
|
|
724
|
+ });
|
|
|
725
|
+ sdp.media.forEach(function(media, idx) {
|
|
|
726
|
+ if (!SDPUtil.find_line(media, 'a=mid:' + name))
|
|
|
727
|
+ return;
|
|
|
728
|
+ sdp.media[idx] += lines;
|
|
|
729
|
+ if (!self.addssrc[idx]) self.addssrc[idx] = '';
|
|
|
730
|
+ self.addssrc[idx] += lines;
|
|
|
731
|
+ });
|
|
|
732
|
+ sdp.raw = sdp.session + sdp.media.join('');
|
|
|
733
|
+ });
|
|
685
|
734
|
this.modifySources();
|
|
686
|
735
|
};
|
|
687
|
736
|
|
|
|
@@ -701,20 +750,165 @@ JingleSession.prototype.removeSource = function (elem, fromJid) {
|
|
701
|
750
|
return;
|
|
702
|
751
|
}
|
|
703
|
752
|
|
|
704
|
|
- this.peerconnection.removeSource(elem);
|
|
705
|
|
-
|
|
|
753
|
+ console.log('removessrc', new Date().getTime());
|
|
|
754
|
+ console.log('ice', this.peerconnection.iceConnectionState);
|
|
|
755
|
+ var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
|
|
|
756
|
+ var mySdp = new SDP(this.peerconnection.localDescription.sdp);
|
|
|
757
|
+
|
|
|
758
|
+ $(elem).each(function (idx, content) {
|
|
|
759
|
+ var name = $(content).attr('name');
|
|
|
760
|
+ var lines = '';
|
|
|
761
|
+ tmp = $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
|
|
|
762
|
+ var semantics = this.getAttribute('semantics');
|
|
|
763
|
+ var ssrcs = $(this).find('>source').map(function () {
|
|
|
764
|
+ return this.getAttribute('ssrc');
|
|
|
765
|
+ }).get();
|
|
|
766
|
+
|
|
|
767
|
+ if (ssrcs.length != 0) {
|
|
|
768
|
+ lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
|
|
|
769
|
+ }
|
|
|
770
|
+ });
|
|
|
771
|
+ tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
|
|
|
772
|
+ tmp.each(function () {
|
|
|
773
|
+ var ssrc = $(this).attr('ssrc');
|
|
|
774
|
+ // This should never happen, but can be useful for bug detection
|
|
|
775
|
+ if(mySdp.containsSSRC(ssrc)){
|
|
|
776
|
+ console.error("Got remove stream request for my own ssrc: "+ssrc);
|
|
|
777
|
+ return;
|
|
|
778
|
+ }
|
|
|
779
|
+ $(this).find('>parameter').each(function () {
|
|
|
780
|
+ lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
|
|
|
781
|
+ if ($(this).attr('value') && $(this).attr('value').length)
|
|
|
782
|
+ lines += ':' + $(this).attr('value');
|
|
|
783
|
+ lines += '\r\n';
|
|
|
784
|
+ });
|
|
|
785
|
+ });
|
|
|
786
|
+ sdp.media.forEach(function(media, idx) {
|
|
|
787
|
+ if (!SDPUtil.find_line(media, 'a=mid:' + name))
|
|
|
788
|
+ return;
|
|
|
789
|
+ sdp.media[idx] += lines;
|
|
|
790
|
+ if (!self.removessrc[idx]) self.removessrc[idx] = '';
|
|
|
791
|
+ self.removessrc[idx] += lines;
|
|
|
792
|
+ });
|
|
|
793
|
+ sdp.raw = sdp.session + sdp.media.join('');
|
|
|
794
|
+ });
|
|
706
|
795
|
this.modifySources();
|
|
707
|
796
|
};
|
|
708
|
797
|
|
|
709
|
798
|
JingleSession.prototype.modifySources = function (successCallback) {
|
|
710
|
799
|
var self = this;
|
|
711
|
|
- if(this.peerconnection)
|
|
712
|
|
- this.peerconnection.modifySources(function(){
|
|
713
|
|
- $(document).trigger('setLocalDescription.jingle', [self.sid]);
|
|
714
|
|
- if(successCallback) {
|
|
715
|
|
- successCallback();
|
|
716
|
|
- }
|
|
|
800
|
+ if (this.peerconnection.signalingState == 'closed') return;
|
|
|
801
|
+ if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
|
|
|
802
|
+ // There is nothing to do since scheduled job might have been executed by another succeeding call
|
|
|
803
|
+ $(document).trigger('setLocalDescription.jingle', [self.sid]);
|
|
|
804
|
+ if(successCallback){
|
|
|
805
|
+ successCallback();
|
|
|
806
|
+ }
|
|
|
807
|
+ return;
|
|
|
808
|
+ }
|
|
|
809
|
+
|
|
|
810
|
+ // FIXME: this is a big hack
|
|
|
811
|
+ // https://code.google.com/p/webrtc/issues/detail?id=2688
|
|
|
812
|
+ // ^ has been fixed.
|
|
|
813
|
+ if (!(this.peerconnection.signalingState == 'stable' && this.peerconnection.iceConnectionState == 'connected')) {
|
|
|
814
|
+ console.warn('modifySources not yet', this.peerconnection.signalingState, this.peerconnection.iceConnectionState);
|
|
|
815
|
+ this.wait = true;
|
|
|
816
|
+ window.setTimeout(function() { self.modifySources(successCallback); }, 250);
|
|
|
817
|
+ return;
|
|
|
818
|
+ }
|
|
|
819
|
+ if (this.wait) {
|
|
|
820
|
+ window.setTimeout(function() { self.modifySources(successCallback); }, 2500);
|
|
|
821
|
+ this.wait = false;
|
|
|
822
|
+ return;
|
|
|
823
|
+ }
|
|
|
824
|
+
|
|
|
825
|
+ // Reset switch streams flag
|
|
|
826
|
+ this.switchstreams = false;
|
|
|
827
|
+
|
|
|
828
|
+ var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
|
|
|
829
|
+
|
|
|
830
|
+ // add sources
|
|
|
831
|
+ this.addssrc.forEach(function(lines, idx) {
|
|
|
832
|
+ sdp.media[idx] += lines;
|
|
|
833
|
+ });
|
|
|
834
|
+ this.addssrc = [];
|
|
|
835
|
+
|
|
|
836
|
+ // remove sources
|
|
|
837
|
+ this.removessrc.forEach(function(lines, idx) {
|
|
|
838
|
+ lines = lines.split('\r\n');
|
|
|
839
|
+ lines.pop(); // remove empty last element;
|
|
|
840
|
+ lines.forEach(function(line) {
|
|
|
841
|
+ sdp.media[idx] = sdp.media[idx].replace(line + '\r\n', '');
|
|
717
|
842
|
});
|
|
|
843
|
+ });
|
|
|
844
|
+ this.removessrc = [];
|
|
|
845
|
+
|
|
|
846
|
+ // FIXME:
|
|
|
847
|
+ // this was a hack for the situation when only one peer exists
|
|
|
848
|
+ // in the conference.
|
|
|
849
|
+ // check if still required and remove
|
|
|
850
|
+ if (sdp.media[0])
|
|
|
851
|
+ sdp.media[0] = sdp.media[0].replace('a=recvonly', 'a=sendrecv');
|
|
|
852
|
+ if (sdp.media[1])
|
|
|
853
|
+ sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
|
|
|
854
|
+
|
|
|
855
|
+ sdp.raw = sdp.session + sdp.media.join('');
|
|
|
856
|
+ this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.raw}),
|
|
|
857
|
+ function() {
|
|
|
858
|
+
|
|
|
859
|
+ if(self.signalingState == 'closed') {
|
|
|
860
|
+ console.error("createAnswer attempt on closed state");
|
|
|
861
|
+ return;
|
|
|
862
|
+ }
|
|
|
863
|
+
|
|
|
864
|
+ self.peerconnection.createAnswer(
|
|
|
865
|
+ function(modifiedAnswer) {
|
|
|
866
|
+ // change video direction, see https://github.com/jitsi/jitmeet/issues/41
|
|
|
867
|
+ if (self.pendingop !== null) {
|
|
|
868
|
+ var sdp = new SDP(modifiedAnswer.sdp);
|
|
|
869
|
+ if (sdp.media.length > 1) {
|
|
|
870
|
+ switch(self.pendingop) {
|
|
|
871
|
+ case 'mute':
|
|
|
872
|
+ sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
|
|
|
873
|
+ break;
|
|
|
874
|
+ case 'unmute':
|
|
|
875
|
+ sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
|
|
|
876
|
+ break;
|
|
|
877
|
+ }
|
|
|
878
|
+ sdp.raw = sdp.session + sdp.media.join('');
|
|
|
879
|
+ modifiedAnswer.sdp = sdp.raw;
|
|
|
880
|
+ }
|
|
|
881
|
+ self.pendingop = null;
|
|
|
882
|
+ }
|
|
|
883
|
+
|
|
|
884
|
+ // FIXME: pushing down an answer while ice connection state
|
|
|
885
|
+ // is still checking is bad...
|
|
|
886
|
+ //console.log(self.peerconnection.iceConnectionState);
|
|
|
887
|
+
|
|
|
888
|
+ // trying to work around another chrome bug
|
|
|
889
|
+ //modifiedAnswer.sdp = modifiedAnswer.sdp.replace(/a=setup:active/g, 'a=setup:actpass');
|
|
|
890
|
+ self.peerconnection.setLocalDescription(modifiedAnswer,
|
|
|
891
|
+ function() {
|
|
|
892
|
+ //console.log('modified setLocalDescription ok');
|
|
|
893
|
+ $(document).trigger('setLocalDescription.jingle', [self.sid]);
|
|
|
894
|
+ if(successCallback){
|
|
|
895
|
+ successCallback();
|
|
|
896
|
+ }
|
|
|
897
|
+ },
|
|
|
898
|
+ function(error) {
|
|
|
899
|
+ console.error('modified setLocalDescription failed', error);
|
|
|
900
|
+ }
|
|
|
901
|
+ );
|
|
|
902
|
+ },
|
|
|
903
|
+ function(error) {
|
|
|
904
|
+ console.error('modified answer failed', error);
|
|
|
905
|
+ }
|
|
|
906
|
+ );
|
|
|
907
|
+ },
|
|
|
908
|
+ function(error) {
|
|
|
909
|
+ console.error('modify failed', error);
|
|
|
910
|
+ }
|
|
|
911
|
+ );
|
|
718
|
912
|
};
|
|
719
|
913
|
|
|
720
|
914
|
/**
|
|
|
@@ -755,7 +949,7 @@ JingleSession.prototype.switchStreams = function (new_stream, oldStream, success
|
|
755
|
949
|
return;
|
|
756
|
950
|
}
|
|
757
|
951
|
|
|
758
|
|
- self.peerconnection.switchstreams = true;
|
|
|
952
|
+ self.switchstreams = true;
|
|
759
|
953
|
self.modifySources(function() {
|
|
760
|
954
|
console.log('modify sources done');
|
|
761
|
955
|
|
|
|
@@ -961,9 +1155,7 @@ JingleSession.prototype.setVideoMute = function (mute, callback, options) {
|
|
961
|
1155
|
tracks[i].enabled = !mute;
|
|
962
|
1156
|
}
|
|
963
|
1157
|
|
|
964
|
|
- if (this.peerconnection) {
|
|
965
|
|
- this.peerconnection.hardMuteVideo(mute);
|
|
966
|
|
- }
|
|
|
1158
|
+ this.hardMuteVideo(mute);
|
|
967
|
1159
|
|
|
968
|
1160
|
this.modifySources(callback(mute));
|
|
969
|
1161
|
}
|
|
|
@@ -975,6 +1167,10 @@ JingleSession.prototype.toggleVideoMute = function (callback) {
|
|
975
|
1167
|
setVideoMute(isVideoMute(), callback);
|
|
976
|
1168
|
};
|
|
977
|
1169
|
|
|
|
1170
|
+JingleSession.prototype.hardMuteVideo = function (muted) {
|
|
|
1171
|
+ this.pendingop = muted ? 'mute' : 'unmute';
|
|
|
1172
|
+};
|
|
|
1173
|
+
|
|
978
|
1174
|
JingleSession.prototype.sendMute = function (muted, content) {
|
|
979
|
1175
|
var info = $iq({to: this.peerjid,
|
|
980
|
1176
|
type: 'set'})
|
|
|
@@ -1046,3 +1242,4 @@ JingleSession.prototype.getStats = function (interval) {
|
|
1046
|
1242
|
}, interval || 3000);
|
|
1047
|
1243
|
return this.statsinterval;
|
|
1048
|
1244
|
};
|
|
|
1245
|
+
|