diff --git a/app.js b/app.js
index db471bd9b..8584d50d5 100644
--- a/app.js
+++ b/app.js
@@ -470,6 +470,12 @@ $(document).bind('callincoming.jingle', function (event, sid) {
// TODO: do we check activecall == null?
activecall = sess;
+ // Bind data channel listener in case we're a regular participant
+ if (config.openSctp)
+ {
+ bindDataChannelListener(sess.peerconnection);
+ }
+
// TODO: check affiliation and/or role
console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
sess.usedrip = true; // not-so-naive trickle ice
@@ -478,6 +484,15 @@ $(document).bind('callincoming.jingle', function (event, sid) {
});
+$(document).bind('conferenceCreated.jingle', function (event, focus)
+{
+ // Bind data channel listener in case we're the focus
+ if (config.openSctp)
+ {
+ bindDataChannelListener(focus.peerconnection);
+ }
+});
+
$(document).bind('callactive.jingle', function (event, videoelem, sid) {
if (videoelem.attr('id').indexOf('mixedmslabel') === -1) {
// ignore mixedmslabela0 and v0
@@ -507,7 +522,7 @@ $(document).bind('setLocalDescription.jingle', function (event, sid) {
var directions = {};
var localSDP = new SDP(sess.peerconnection.localDescription.sdp);
localSDP.media.forEach(function (media) {
- var type = SDPUtil.parse_mline(media.split('\r\n')[0]).media;
+ var type = SDPUtil.parse_mid(SDPUtil.find_line(media, 'a=mid:'));
if (SDPUtil.find_line(media, 'a=ssrc:')) {
// assumes a single local ssrc
diff --git a/config.js b/config.js
index be326bf83..c34a2bfd5 100644
--- a/config.js
+++ b/config.js
@@ -11,5 +11,6 @@ var config = {
bosh: '//lambada.jitsi.net/http-bind', // FIXME: use xep-0156 for that
desktopSharing: 'ext', // Desktop sharing method. Can be set to 'ext', 'webrtc' or false to disable.
chromeExtensionId: 'diibjkoicjeejcmhdnailmkgecihlobk', // Id of desktop streamer Chrome extension
- minChromeExtVersion: '0.1' // Required version of Chrome extension
+ minChromeExtVersion: '0.1', // Required version of Chrome extension
+ openSctp: true //Toggle to enable/disable SCTP channels
};
\ No newline at end of file
diff --git a/data_channels.js b/data_channels.js
new file mode 100644
index 000000000..38d70741e
--- /dev/null
+++ b/data_channels.js
@@ -0,0 +1,63 @@
+/**
+ * Callback triggered by PeerConnection when new data channel is opened
+ * on the bridge.
+ * @param event the event info object.
+ */
+function onDataChannel(event)
+{
+ var dataChannel = event.channel;
+
+ dataChannel.onopen = function ()
+ {
+ console.info("Data channel opened by the bridge !!!", dataChannel);
+
+ // Sends String message to the bridge
+ dataChannel.send("Hello bridge!");
+
+ // Sends 12 bytes binary message to the bridge
+ dataChannel.send(new ArrayBuffer(12));
+ };
+
+ dataChannel.onerror = function (error)
+ {
+ console.error("Data Channel Error:", error, dataChannel);
+ };
+
+ dataChannel.onmessage = function (event)
+ {
+ var msgData = event.data;
+ console.info("Got Data Channel Message:", msgData, dataChannel);
+ };
+
+ dataChannel.onclose = function ()
+ {
+ console.info("The Data Channel closed", dataChannel);
+ };
+}
+
+/**
+ * Binds "ondatachannel" event listener to given PeerConnection instance.
+ * @param peerConnection WebRTC peer connection instance.
+ */
+function bindDataChannelListener(peerConnection)
+{
+ peerConnection.ondatachannel = onDataChannel;
+
+ // Sample code for opening new data channel from Jitsi Meet to the bridge.
+ // Although it's not a requirement to open separate channels from both bridge
+ // and peer as single channel can be used for sending and receiving data.
+ // So either channel opened by the bridge or the one opened here is enough
+ // for communication with the bridge.
+ var dataChannelOptions =
+ {
+ reliable: true
+ };
+ var dataChannel
+ = peerConnection.createDataChannel("myChannel", dataChannelOptions);
+
+ // Can be used only when is in open state
+ dataChannel.onopen = function ()
+ {
+ dataChannel.send("My channel !!!");
+ };
+}
\ No newline at end of file
diff --git a/index.html b/index.html
index d323dd1de..b11b1e23f 100644
--- a/index.html
+++ b/index.html
@@ -24,6 +24,7 @@
+
diff --git a/libs/colibri/colibri.focus.js b/libs/colibri/colibri.focus.js
index 7643f4a24..2fac9aaf7 100644
--- a/libs/colibri/colibri.focus.js
+++ b/libs/colibri/colibri.focus.js
@@ -44,8 +44,21 @@ function ColibriFocus(connection, bridgejid) {
this.peers = [];
this.confid = null;
+ /**
+ * Default channel expire value in seconds.
+ * @type {number}
+ */
+ this.channelExpire = 60;
+
// media types of the conference
- this.media = ['audio', 'video'];
+ if (config.openSctp)
+ {
+ this.media = ['audio', 'video', 'data'];
+ }
+ else
+ {
+ this.media = ['audio', 'video'];
+ }
this.connection.jingle.sessions[this.sid] = this;
this.mychannel = [];
@@ -151,17 +164,29 @@ ColibriFocus.prototype._makeConference = function () {
elem.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri'});
this.media.forEach(function (name) {
+ var isData = name === 'data';
+ var channel = isData ? 'sctpconnection' : 'channel';
+
elem.c('content', {name: name});
- elem.c('channel', {
+
+ elem.c(channel, {
initiator: 'true',
expire: '15',
- endpoint: 'fix_me_focus_endpoint'}).up();
+ endpoint: 'fix_me_focus_endpoint'
+ });
+ if (isData)
+ elem.attrs({port: 5000});
+ elem.up();// end of channel
+
for (var j = 0; j < self.peers.length; j++) {
- elem.c('channel', {
+ elem.c(channel, {
initiator: 'true',
expire: '15',
endpoint: self.peers[j].substr(1 + self.peers[j].lastIndexOf('/'))
- }).up();
+ });
+ if (isData)
+ elem.attrs({port: 5000});
+ elem.up(); // end of channel
}
elem.up(); // end of content
});
@@ -209,8 +234,13 @@ ColibriFocus.prototype.createdConference = function (result) {
this.confid = $(result).find('>conference').attr('id');
var remotecontents = $(result).find('>conference>content').get();
var numparticipants = 0;
- for (var i = 0; i < remotecontents.length; i++) {
- tmp = $(remotecontents[i]).find('>channel').get();
+ for (var i = 0; i < remotecontents.length; i++)
+ {
+ var contentName = $(remotecontents[i]).attr('name');
+ var channelName
+ = contentName !== 'data' ? '>channel' : '>sctpconnection';
+
+ tmp = $(remotecontents[i]).find(channelName).get();
this.mychannel.push($(tmp.shift()));
numparticipants = tmp.length;
for (j = 0; j < tmp.length; j++) {
@@ -223,7 +253,55 @@ ColibriFocus.prototype.createdConference = function (result) {
console.log('remote channels', this.channels);
- var bridgeSDP = new SDP('v=0\r\no=- 5151055458874951233 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\nm=audio 1 RTP/SAVPF 111 103 104 0 8 106 105 13 126\r\nc=IN IP4 0.0.0.0\r\na=rtcp:1 IN IP4 0.0.0.0\r\na=mid:audio\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=sendrecv\r\na=rtpmap:111 opus/48000/2\r\na=fmtp:111 minptime=10\r\na=rtpmap:103 ISAC/16000\r\na=rtpmap:104 ISAC/32000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:106 CN/32000\r\na=rtpmap:105 CN/16000\r\na=rtpmap:13 CN/8000\r\na=rtpmap:126 telephone-event/8000\r\na=maxptime:60\r\nm=video 1 RTP/SAVPF 100 116 117\r\nc=IN IP4 0.0.0.0\r\na=rtcp:1 IN IP4 0.0.0.0\r\na=mid:video\r\na=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=sendrecv\r\na=rtpmap:100 VP8/90000\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 goog-remb\r\na=rtpmap:116 red/90000\r\na=rtpmap:117 ulpfec/90000\r\n');
+ // Notify that the focus has created the conference on the bridge
+ $(document).trigger('conferenceCreated.jingle', [self]);
+
+ var bridgeSDP = new SDP(
+ 'v=0\r\n' +
+ 'o=- 5151055458874951233 2 IN IP4 127.0.0.1\r\n' +
+ 's=-\r\n' +
+ 't=0 0\r\n' +
+ /* Audio */
+ 'm=audio 1 RTP/SAVPF 111 103 104 0 8 106 105 13 126\r\n' +
+ 'c=IN IP4 0.0.0.0\r\n' +
+ 'a=rtcp:1 IN IP4 0.0.0.0\r\n' +
+ 'a=mid:audio\r\n' +
+ 'a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n' +
+ 'a=sendrecv\r\n' +
+ 'a=rtpmap:111 opus/48000/2\r\n' +
+ 'a=fmtp:111 minptime=10\r\n' +
+ 'a=rtpmap:103 ISAC/16000\r\n' +
+ 'a=rtpmap:104 ISAC/32000\r\n' +
+ 'a=rtpmap:0 PCMU/8000\r\n' +
+ 'a=rtpmap:8 PCMA/8000\r\n' +
+ 'a=rtpmap:106 CN/32000\r\n' +
+ 'a=rtpmap:105 CN/16000\r\n' +
+ 'a=rtpmap:13 CN/8000\r\n' +
+ 'a=rtpmap:126 telephone-event/8000\r\n' +
+ 'a=maxptime:60\r\n' +
+ /* Video */
+ 'm=video 1 RTP/SAVPF 100 116 117\r\n' +
+ 'c=IN IP4 0.0.0.0\r\n' +
+ 'a=rtcp:1 IN IP4 0.0.0.0\r\n' +
+ 'a=mid:video\r\n' +
+ 'a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\n' +
+ 'a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n' +
+ 'a=sendrecv\r\n' +
+ 'a=rtpmap:100 VP8/90000\r\n' +
+ 'a=rtcp-fb:100 ccm fir\r\n' +
+ 'a=rtcp-fb:100 nack\r\n' +
+ 'a=rtcp-fb:100 goog-remb\r\n' +
+ 'a=rtpmap:116 red/90000\r\n' +
+ 'a=rtpmap:117 ulpfec/90000\r\n' +
+ /* Data SCTP */
+ (config.openSctp ?
+ 'm=application 1 DTLS/SCTP 5000\r\n' +
+ 'c=IN IP4 0.0.0.0\r\n' +
+ 'a=sctpmap:5000 webrtc-datachannel\r\n' +
+ 'a=mid:data\r\n'
+ : '')
+ );
+
bridgeSDP.media.length = this.mychannel.length;
var channel;
/*
@@ -262,12 +340,17 @@ ColibriFocus.prototype.createdConference = function (result) {
// get the mixed ssrc
tmp = $(this.mychannel[channel]).find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
// FIXME: check rtp-level-relay-type
- if (tmp.length) {
+
+ var isData = bridgeSDP.media[channel].indexOf('application') !== -1;
+ if (!isData && tmp.length)
+ {
bridgeSDP.media[channel] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'cname:mixed' + '\r\n';
bridgeSDP.media[channel] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'label:mixedlabela0' + '\r\n';
bridgeSDP.media[channel] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'msid:mixedmslabel mixedlabela0' + '\r\n';
bridgeSDP.media[channel] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'mslabel:mixedmslabel' + '\r\n';
- } else {
+ }
+ else if (!isData)
+ {
// make chrome happy... '3735928559' == 0xDEADBEEF
// FIXME: this currently appears as two streams, should be one
bridgeSDP.media[channel] += 'a=ssrc:' + '3735928559' + ' ' + 'cname:mixed' + '\r\n';
@@ -308,21 +391,41 @@ ColibriFocus.prototype.createdConference = function (result) {
elem.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri', id: self.confid});
var localSDP = new SDP(self.peerconnection.localDescription.sdp);
localSDP.media.forEach(function (media, channel) {
- var name = SDPUtil.parse_mline(media.split('\r\n')[0]).media;
+ var name = SDPUtil.parse_mid(SDPUtil.find_line(media, 'a=mid:'));
elem.c('content', {name: name});
- elem.c('channel', {
- initiator: 'true',
- expire: '15',
- id: self.mychannel[channel].attr('id'),
- endpoint: 'fix_me_focus_endpoint'
- });
-
- // FIXME: should reuse code from .toJingle
var mline = SDPUtil.parse_mline(media.split('\r\n')[0]);
- for (var j = 0; j < mline.fmt.length; j++) {
- var rtpmap = SDPUtil.find_line(media, 'a=rtpmap:' + mline.fmt[j]);
- elem.c('payload-type', SDPUtil.parse_rtpmap(rtpmap));
- elem.up();
+ if (name !== 'data')
+ {
+ elem.c('channel', {
+ initiator: 'true',
+ expire: self.channelExpire,
+ id: self.mychannel[channel].attr('id'),
+ endpoint: 'fix_me_focus_endpoint'
+ });
+
+ // FIXME: should reuse code from .toJingle
+ for (var j = 0; j < mline.fmt.length; j++)
+ {
+ var rtpmap = SDPUtil.find_line(media, 'a=rtpmap:' + mline.fmt[j]);
+ if (rtpmap)
+ {
+ elem.c('payload-type', SDPUtil.parse_rtpmap(rtpmap));
+ elem.up();
+ }
+ }
+ }
+ else
+ {
+ var sctpmap = SDPUtil.find_line(media, 'a=sctpmap:' + mline.fmt[0]);
+ var sctpPort = SDPUtil.parse_sctpmap(sctpmap);
+ elem.c("sctpconnection",
+ {
+ initiator: 'true',
+ expire: self.channelExpire,
+ endpoint: 'fix_me_focus_endpoint',
+ port: sctpPort
+ }
+ );
}
localSDP.TransportToJingle(channel, elem);
@@ -336,7 +439,9 @@ ColibriFocus.prototype.createdConference = function (result) {
// ...
},
function (error) {
- console.warn(error);
+ console.error(
+ "ERROR setLocalDescription succeded",
+ error, elem);
}
);
@@ -417,7 +522,10 @@ ColibriFocus.prototype.initiate = function (peer, isInitiator) {
sdp.media[j] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'label:mixedlabela0' + '\r\n';
sdp.media[j] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'msid:mixedmslabel mixedlabela0' + '\r\n';
sdp.media[j] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'mslabel:mixedmslabel' + '\r\n';
- } else {
+ }
+ // No SSRCs for 'data', comes when j == 2
+ else if (j < 2)
+ {
// make chrome happy... '3735928559' == 0xDEADBEEF
sdp.media[j] += 'a=ssrc:' + '3735928559' + ' ' + 'cname:mixed' + '\r\n';
sdp.media[j] += 'a=ssrc:' + '3735928559' + ' ' + 'label:mixedlabelv0' + '\r\n';
@@ -486,9 +594,17 @@ ColibriFocus.prototype.initiate = function (peer, isInitiator) {
// pull in a new participant into the conference
ColibriFocus.prototype.addNewParticipant = function (peer) {
var self = this;
- if (this.confid === 0) {
+ if (this.confid === 0 || !this.peerconnection.localDescription)
+ {
// bad state
- console.log('confid does not exist yet, postponing', peer);
+ if (this.confid === 0)
+ {
+ console.error('confid does not exist yet, postponing', peer);
+ }
+ else
+ {
+ console.error('local description not ready yet, postponing', peer);
+ }
window.setTimeout(function () {
self.addNewParticipant(peer);
}, 250);
@@ -502,14 +618,26 @@ ColibriFocus.prototype.addNewParticipant = function (peer) {
elem.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri', id: this.confid});
var localSDP = new SDP(this.peerconnection.localDescription.sdp);
localSDP.media.forEach(function (media, channel) {
- var name = SDPUtil.parse_mline(media.split('\r\n')[0]).media;
+ var name = SDPUtil.parse_mid(SDPUtil.find_line(media, 'a=mid:'));
elem.c('content', {name: name});
- elem.c('channel', {
+ if (name !== 'data')
+ {
+ elem.c('channel', {
initiator: 'true',
- expire:'15',
+ expire: self.channelExpire,
endpoint: peer.substr(1 + peer.lastIndexOf('/'))
- });
- elem.up(); // end of channel
+ });
+ }
+ else
+ {
+ elem.c('sctpconnection', {
+ endpoint: peer.substr(1 + peer.lastIndexOf('/')),
+ initiator: 'true',
+ expire: self.channelExpire,
+ port: 5000
+ });
+ }
+ elem.up(); // end of channel/sctpconnection
elem.up(); // end of content
});
@@ -517,7 +645,15 @@ ColibriFocus.prototype.addNewParticipant = function (peer) {
function (result) {
var contents = $(result).find('>conference>content').get();
for (var i = 0; i < contents.length; i++) {
- tmp = $(contents[i]).find('>channel').get();
+ var channelXml = $(contents[i]).find('>channel');
+ if (channelXml.length)
+ {
+ tmp = channelXml.get();
+ }
+ else
+ {
+ tmp = $(contents[i]).find('>sctpconnection').get();
+ }
self.channels[index][i] = tmp[0];
}
self.initiate(peer, true);
@@ -531,37 +667,52 @@ ColibriFocus.prototype.addNewParticipant = function (peer) {
// update the channel description (payload-types + dtls fp) for a participant
ColibriFocus.prototype.updateChannel = function (remoteSDP, participant) {
console.log('change allocation for', this.confid);
+ var self = this;
var change = $iq({to: this.bridgejid, type: 'set'});
change.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri', id: this.confid});
- for (channel = 0; channel < this.channels[participant].length; channel++) {
- change.c('content', {name: channel === 0 ? 'audio' : 'video'});
- change.c('channel', {
- id: $(this.channels[participant][channel]).attr('id'),
- endpoint: $(this.channels[participant][channel]).attr('endpoint'),
- expire: '15'
- });
+ for (channel = 0; channel < this.channels[participant].length; channel++)
+ {
+ var name = SDPUtil.parse_mid(SDPUtil.find_line(remoteSDP.media[channel], 'a=mid:'));
+ change.c('content', {name: name});
+ if (name !== 'data')
+ {
+ change.c('channel', {
+ id: $(this.channels[participant][channel]).attr('id'),
+ endpoint: $(this.channels[participant][channel]).attr('endpoint'),
+ expire: self.channelExpire
+ });
- var rtpmap = SDPUtil.find_lines(remoteSDP.media[channel], 'a=rtpmap:');
- rtpmap.forEach(function (val) {
- // TODO: too much copy-paste
- var rtpmap = SDPUtil.parse_rtpmap(val);
- change.c('payload-type', rtpmap);
- //
- // put any 'a=fmtp:' + mline.fmt[j] lines into
- /*
- if (SDPUtil.find_line(remoteSDP.media[channel], 'a=fmtp:' + rtpmap.id)) {
- tmp = SDPUtil.parse_fmtp(SDPUtil.find_line(remoteSDP.media[channel], 'a=fmtp:' + rtpmap.id));
- for (var k = 0; k < tmp.length; k++) {
- change.c('parameter', tmp[k]).up();
+ var rtpmap = SDPUtil.find_lines(remoteSDP.media[channel], 'a=rtpmap:');
+ rtpmap.forEach(function (val) {
+ // TODO: too much copy-paste
+ var rtpmap = SDPUtil.parse_rtpmap(val);
+ change.c('payload-type', rtpmap);
+ //
+ // put any 'a=fmtp:' + mline.fmt[j] lines into
+ /*
+ if (SDPUtil.find_line(remoteSDP.media[channel], 'a=fmtp:' + rtpmap.id)) {
+ tmp = SDPUtil.parse_fmtp(SDPUtil.find_line(remoteSDP.media[channel], 'a=fmtp:' + rtpmap.id));
+ for (var k = 0; k < tmp.length; k++) {
+ change.c('parameter', tmp[k]).up();
+ }
}
- }
- */
- change.up();
- });
+ */
+ change.up();
+ });
+ }
+ else
+ {
+ var sctpmap = SDPUtil.find_line(remoteSDP.media[channel], 'a=sctpmap:');
+ change.c('sctpconnection', {
+ endpoint: $(this.channels[participant][channel]).attr('endpoint'),
+ expire: self.channelExpire,
+ port: SDPUtil.parse_sctpmap(sctpmap)
+ });
+ }
// now add transport
remoteSDP.TransportToJingle(channel, change);
- change.up(); // end of channel
+ change.up(); // end of channel/sctpconnection
change.up(); // end of content
}
this.connection.sendIQ(change,
@@ -675,8 +826,11 @@ ColibriFocus.prototype.setRemoteDescription = function (session, elem, desctype)
this.remotessrc[session.peerjid] = [];
for (channel = 0; channel < this.channels[participant].length; channel++) {
//if (channel == 0) continue; FIXME: does not work as intended
- if (SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc:').length) {
- this.remotessrc[session.peerjid][channel] = SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc:').join('\r\n') + '\r\n';
+ if (SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc:').length)
+ {
+ this.remotessrc[session.peerjid][channel] =
+ SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc:')
+ .join('\r\n') + '\r\n';
}
}
@@ -702,14 +856,27 @@ ColibriFocus.prototype.addIceCandidate = function (session, elem) {
change.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri', id: this.confid});
$(elem).each(function () {
var name = $(this).attr('name');
+
var channel = name == 'audio' ? 0 : 1; // FIXME: search mlineindex in localdesc
+ if (name != 'audio' && name != 'video')
+ channel = 2; // name == 'data'
change.c('content', {name: name});
- change.c('channel', {
- id: $(self.channels[participant][channel]).attr('id'),
- endpoint: $(self.channels[participant][channel]).attr('endpoint'),
- expire: '15'
- });
+ if (name !== 'data')
+ {
+ change.c('channel', {
+ id: $(self.channels[participant][channel]).attr('id'),
+ endpoint: $(self.channels[participant][channel]).attr('endpoint'),
+ expire: self.channelExpire
+ });
+ }
+ else
+ {
+ change.c('sctpconnection', {
+ endpoint: $(self.channels[participant][channel]).attr('endpoint'),
+ expire: self.channelExpire
+ });
+ }
$(this).find('>transport').each(function () {
change.c('transport', {
ufrag: $(this).attr('ufrag'),
@@ -729,7 +896,7 @@ ColibriFocus.prototype.addIceCandidate = function (session, elem) {
});
change.up(); // end of transport
});
- change.up(); // end of channel
+ change.up(); // end of channel/sctpconnection
change.up(); // end of content
});
// FIXME: need to check if there is at least one candidate when filtering TCP ones
@@ -769,21 +936,35 @@ ColibriFocus.prototype.sendIceCandidates = function (candidates) {
mycands.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri', id: this.confid});
// FIXME: multi-candidate logic is taken from strophe.jingle, should be refactored there
var localSDP = new SDP(this.peerconnection.localDescription.sdp);
- for (var mid = 0; mid < localSDP.media.length; mid++) {
+ for (var mid = 0; mid < localSDP.media.length; mid++)
+ {
var cands = candidates.filter(function (el) { return el.sdpMLineIndex == mid; });
- if (cands.length > 0) {
- mycands.c('content', {name: cands[0].sdpMid });
- mycands.c('channel', {
- id: $(this.mychannel[cands[0].sdpMLineIndex]).attr('id'),
- endpoint: $(this.mychannel[cands[0].sdpMLineIndex]).attr('endpoint'),
- expire: '15'
- });
+ if (cands.length > 0)
+ {
+ var name = cands[0].sdpMid;
+ mycands.c('content', {name: name });
+ if (name !== 'data')
+ {
+ mycands.c('channel', {
+ id: $(this.mychannel[cands[0].sdpMLineIndex]).attr('id'),
+ endpoint: $(this.mychannel[cands[0].sdpMLineIndex]).attr('endpoint'),
+ expire: self.channelExpire
+ });
+ }
+ else
+ {
+ mycands.c('sctpconnection', {
+ endpoint: $(this.mychannel[cands[0].sdpMLineIndex]).attr('endpoint'),
+ port: $(this.mychannel[cands[0].sdpMLineIndex]).attr('port'),
+ expire: self.channelExpire
+ });
+ }
mycands.c('transport', {xmlns: 'urn:xmpp:jingle:transports:ice-udp:1'});
for (var i = 0; i < cands.length; i++) {
mycands.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up();
}
mycands.up(); // transport
- mycands.up(); // channel
+ mycands.up(); // channel / sctpconnection
mycands.up(); // content
}
}
@@ -814,13 +995,26 @@ ColibriFocus.prototype.terminate = function (session, reason) {
var change = $iq({to: this.bridgejid, type: 'set'});
change.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri', id: this.confid});
for (var channel = 0; channel < this.channels[participant].length; channel++) {
- change.c('content', {name: channel === 0 ? 'audio' : 'video'});
- change.c('channel', {
- id: $(this.channels[participant][channel]).attr('id'),
- endpoint: $(this.channels[participant][channel]).attr('endpoint'),
- expire: '0'
- });
- change.up(); // end of channel
+ var name = channel === 0 ? 'audio' : 'video';
+ if (channel == 2)
+ name = 'data';
+ change.c('content', {name: name});
+ if (name !== 'data')
+ {
+ change.c('channel', {
+ id: $(this.channels[participant][channel]).attr('id'),
+ endpoint: $(this.channels[participant][channel]).attr('endpoint'),
+ expire: '0'
+ });
+ }
+ else
+ {
+ change.c('sctpconnection', {
+ endpoint: $(this.channels[participant][channel]).attr('endpoint'),
+ expire: '0'
+ });
+ }
+ change.up(); // end of channel/sctpconnection
change.up(); // end of content
}
this.connection.sendIQ(change,
diff --git a/libs/strophe/strophe.jingle.adapter.js b/libs/strophe/strophe.jingle.adapter.js
index 2aa712fc1..8f273ea61 100644
--- a/libs/strophe/strophe.jingle.adapter.js
+++ b/libs/strophe/strophe.jingle.adapter.js
@@ -32,8 +32,8 @@ function TraceablePeerConnection(ice_config, constraints) {
this.switchstreams = false;
// override as desired
- this.trace = function(what, info) {
- //console.warn('WTRACE', what, info);
+ this.trace = function (what, info) {
+ console.warn('WTRACE', what, info);
self.updateLog.push({
time: new Date(),
type: what,
@@ -144,8 +144,8 @@ TraceablePeerConnection.prototype.removeStream = function (stream) {
TraceablePeerConnection.prototype.createDataChannel = function (label, opts) {
this.trace('createDataChannel', label, opts);
- this.peerconnection.createDataChannel(label, opts);
-}
+ return this.peerconnection.createDataChannel(label, opts);
+};
TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) {
var self = this;
diff --git a/libs/strophe/strophe.jingle.sdp.js b/libs/strophe/strophe.jingle.sdp.js
index f7e3c9d56..cdcfd38ef 100644
--- a/libs/strophe/strophe.jingle.sdp.js
+++ b/libs/strophe/strophe.jingle.sdp.js
@@ -155,7 +155,10 @@ SDP.prototype.toJingle = function (elem, thecreator) {
}
for (i = 0; i < this.media.length; i++) {
mline = SDPUtil.parse_mline(this.media[i].split('\r\n')[0]);
- if (!(mline.media == 'audio' || mline.media == 'video')) {
+ if (!(mline.media === 'audio' ||
+ mline.media === 'video' ||
+ mline.media === 'application'))
+ {
continue;
}
if (SDPUtil.find_line(this.media[i], 'a=ssrc:')) {
@@ -171,12 +174,32 @@ SDP.prototype.toJingle = function (elem, thecreator) {
elem.attrs({ name: mid });
// old BUNDLE plan, to be removed
- if (bundle.indexOf(mid) != -1) {
+ if (bundle.indexOf(mid) !== -1) {
elem.c('bundle', {xmlns: 'http://estos.de/ns/bundle'}).up();
bundle.splice(bundle.indexOf(mid), 1);
}
}
- if (SDPUtil.find_line(this.media[i], 'a=rtpmap:').length) {
+ // Sctp
+ if (SDPUtil.find_line(this.media[i], 'a=sctpmap:').length)
+ {
+ for (j = 0; j < mline.fmt.length; j++)
+ {
+ var sctpmap = SDPUtil.find_line(
+ this.media[i], 'a=sctpmap:' + mline.fmt[j]);
+ if (sctpmap)
+ {
+ elem.c('sctp',
+ {
+ xmlns: 'http://jitsi.org/ns/sctp',
+ port : SDPUtil.parse_sctpmap(sctpmap)
+ });
+ elem.up();
+ }
+ }
+ }
+
+ if (SDPUtil.find_line(this.media[i], 'a=rtpmap:').length)
+ {
elem.c('description',
{xmlns: 'urn:xmpp:jingle:apps:rtp:1',
media: mline.media });
@@ -438,6 +461,11 @@ SDP.prototype.jingle2media = function (content) {
ssrc = desc.attr('ssrc'),
self = this,
tmp;
+ var sctp = null;
+ if (!desc.length)
+ {
+ sctp = content.find('sctp');
+ }
tmp = { media: desc.attr('media') };
tmp.port = '1';
@@ -446,14 +474,28 @@ SDP.prototype.jingle2media = function (content) {
tmp.port = '0';
}
if (content.find('>transport>fingerprint').length || desc.find('encryption').length) {
- tmp.proto = 'RTP/SAVPF';
+ if (sctp)
+ tmp.proto = 'DTLS/SCTP';
+ else
+ tmp.proto = 'RTP/SAVPF';
} else {
tmp.proto = 'RTP/AVPF';
}
- tmp.fmt = desc.find('payload-type').map(function () { return this.getAttribute('id'); }).get();
- media += SDPUtil.build_mline(tmp) + '\r\n';
+ if (!sctp)
+ {
+ tmp.fmt = desc.find('payload-type').map(
+ function () { return this.getAttribute('id'); }).get();
+ media += SDPUtil.build_mline(tmp) + '\r\n';
+ }
+ else
+ {
+ media += 'm=application 1 DTLS/SCTP ' + sctp.attr('port') + '\r\n';
+ media += 'a=sctpmap:' + sctp.attr('port') + ' webrtc-datachannel\r\n';
+ }
+
media += 'c=IN IP4 0.0.0.0\r\n';
- media += 'a=rtcp:1 IN IP4 0.0.0.0\r\n';
+ if (!sctp)
+ media += 'a=rtcp:1 IN IP4 0.0.0.0\r\n';
tmp = content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
if (tmp.length) {
if (tmp.attr('ufrag')) {
diff --git a/libs/strophe/strophe.jingle.sdp.util.js b/libs/strophe/strophe.jingle.sdp.util.js
index 7e681acbb..36750d140 100644
--- a/libs/strophe/strophe.jingle.sdp.util.js
+++ b/libs/strophe/strophe.jingle.sdp.util.js
@@ -90,6 +90,16 @@ SDPUtil = {
data.channels = parts.length ? parts.shift() : '1';
return data;
},
+ /**
+ * Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
+ * @param line eg. "a=sctpmap:5000 webrtc-datachannel"
+ * @returns SCTP port number
+ */
+ parse_sctpmap: function (line)
+ {
+ var parts = line.substring(10).split(' ');
+ return parts[0];// SCTP port
+ },
build_rtpmap: function (el) {
var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {