Adds SCTP data channels.

This commit is contained in:
paweldomas 2014-05-08 11:26:15 +02:00 committed by yanas
parent 96b7a3d3e1
commit 0509b8e3c4
8 changed files with 420 additions and 94 deletions

17
app.js
View File

@ -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

View File

@ -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
};

63
data_channels.js Normal file
View File

@ -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 !!!");
};
}

View File

@ -24,6 +24,7 @@
<script src="muc.js?v=10"></script><!-- simple MUC library -->
<script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
<script src="desktopsharing.js?v=1"></script><!-- desktop sharing -->
<script src="data_channels.js?v=1"></script><!-- data channels -->
<script src="app.js?v=26"></script><!-- application logic -->
<script src="chat.js?v=4"></script><!-- chat logic -->
<script src="util.js?v=3"></script><!-- utility functions -->

View File

@ -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 <param name=foo value=bar/>
/*
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 <param name=foo value=bar/>
/*
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,

View File

@ -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;

View File

@ -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')) {

View File

@ -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') {