409 lines
14 KiB
JavaScript
409 lines
14 KiB
JavaScript
/**
|
|
* Contains utility classes used in SDP class.
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* Class holds a=ssrc lines and media type a=mid
|
|
* @param ssrc synchronization source identifier number(a=ssrc lines from SDP)
|
|
* @param type media type eg. "audio" or "video"(a=mid frm SDP)
|
|
* @constructor
|
|
*/
|
|
function ChannelSsrc(ssrc, type) {
|
|
this.ssrc = ssrc;
|
|
this.type = type;
|
|
this.lines = [];
|
|
}
|
|
|
|
/**
|
|
* Class holds a=ssrc-group: lines
|
|
* @param semantics
|
|
* @param ssrcs
|
|
* @constructor
|
|
*/
|
|
function ChannelSsrcGroup(semantics, ssrcs, line) {
|
|
this.semantics = semantics;
|
|
this.ssrcs = ssrcs;
|
|
}
|
|
|
|
/**
|
|
* Helper class represents media channel. Is a container for ChannelSsrc, holds channel idx and media type.
|
|
* @param channelNumber channel idx in SDP media array.
|
|
* @param mediaType media type(a=mid)
|
|
* @constructor
|
|
*/
|
|
function MediaChannel(channelNumber, mediaType) {
|
|
/**
|
|
* SDP channel number
|
|
* @type {*}
|
|
*/
|
|
this.chNumber = channelNumber;
|
|
/**
|
|
* Channel media type(a=mid)
|
|
* @type {*}
|
|
*/
|
|
this.mediaType = mediaType;
|
|
/**
|
|
* The maps of ssrc numbers to ChannelSsrc objects.
|
|
*/
|
|
this.ssrcs = {};
|
|
|
|
/**
|
|
* The array of ChannelSsrcGroup objects.
|
|
* @type {Array}
|
|
*/
|
|
this.ssrcGroups = [];
|
|
}
|
|
|
|
SDPUtil = {
|
|
iceparams: function (mediadesc, sessiondesc) {
|
|
var data = null;
|
|
if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
|
|
SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
|
|
data = {
|
|
ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
|
|
pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
|
|
};
|
|
}
|
|
return data;
|
|
},
|
|
parse_iceufrag: function (line) {
|
|
return line.substring(12);
|
|
},
|
|
build_iceufrag: function (frag) {
|
|
return 'a=ice-ufrag:' + frag;
|
|
},
|
|
parse_icepwd: function (line) {
|
|
return line.substring(10);
|
|
},
|
|
build_icepwd: function (pwd) {
|
|
return 'a=ice-pwd:' + pwd;
|
|
},
|
|
parse_mid: function (line) {
|
|
return line.substring(6);
|
|
},
|
|
parse_mline: function (line) {
|
|
var parts = line.substring(2).split(' '),
|
|
data = {};
|
|
data.media = parts.shift();
|
|
data.port = parts.shift();
|
|
data.proto = parts.shift();
|
|
if (parts[parts.length - 1] === '') { // trailing whitespace
|
|
parts.pop();
|
|
}
|
|
data.fmt = parts;
|
|
return data;
|
|
},
|
|
build_mline: function (mline) {
|
|
return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
|
|
},
|
|
parse_rtpmap: function (line) {
|
|
var parts = line.substring(9).split(' '),
|
|
data = {};
|
|
data.id = parts.shift();
|
|
parts = parts[0].split('/');
|
|
data.name = parts.shift();
|
|
data.clockrate = parts.shift();
|
|
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, protocol, streams]
|
|
*/
|
|
parse_sctpmap: function (line)
|
|
{
|
|
var parts = line.substring(10).split(' ');
|
|
var sctpPort = parts[0];
|
|
var protocol = parts[1];
|
|
// Stream count is optional
|
|
var streamCount = parts.length > 2 ? parts[2] : null;
|
|
return [sctpPort, protocol, streamCount];// 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') {
|
|
line += '/' + el.getAttribute('channels');
|
|
}
|
|
return line;
|
|
},
|
|
parse_crypto: function (line) {
|
|
var parts = line.substring(9).split(' '),
|
|
data = {};
|
|
data.tag = parts.shift();
|
|
data['crypto-suite'] = parts.shift();
|
|
data['key-params'] = parts.shift();
|
|
if (parts.length) {
|
|
data['session-params'] = parts.join(' ');
|
|
}
|
|
return data;
|
|
},
|
|
parse_fingerprint: function (line) { // RFC 4572
|
|
var parts = line.substring(14).split(' '),
|
|
data = {};
|
|
data.hash = parts.shift();
|
|
data.fingerprint = parts.shift();
|
|
// TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
|
|
return data;
|
|
},
|
|
parse_fmtp: function (line) {
|
|
var parts = line.split(' '),
|
|
i, key, value,
|
|
data = [];
|
|
parts.shift();
|
|
parts = parts.join(' ').split(';');
|
|
for (i = 0; i < parts.length; i++) {
|
|
key = parts[i].split('=')[0];
|
|
while (key.length && key[0] == ' ') {
|
|
key = key.substring(1);
|
|
}
|
|
value = parts[i].split('=')[1];
|
|
if (key && value) {
|
|
data.push({name: key, value: value});
|
|
} else if (key) {
|
|
// rfc 4733 (DTMF) style stuff
|
|
data.push({name: '', value: key});
|
|
}
|
|
}
|
|
return data;
|
|
},
|
|
parse_icecandidate: function (line) {
|
|
var candidate = {},
|
|
elems = line.split(' ');
|
|
candidate.foundation = elems[0].substring(12);
|
|
candidate.component = elems[1];
|
|
candidate.protocol = elems[2].toLowerCase();
|
|
candidate.priority = elems[3];
|
|
candidate.ip = elems[4];
|
|
candidate.port = elems[5];
|
|
// elems[6] => "typ"
|
|
candidate.type = elems[7];
|
|
candidate.generation = 0; // default value, may be overwritten below
|
|
for (var i = 8; i < elems.length; i += 2) {
|
|
switch (elems[i]) {
|
|
case 'raddr':
|
|
candidate['rel-addr'] = elems[i + 1];
|
|
break;
|
|
case 'rport':
|
|
candidate['rel-port'] = elems[i + 1];
|
|
break;
|
|
case 'generation':
|
|
candidate.generation = elems[i + 1];
|
|
break;
|
|
case 'tcptype':
|
|
candidate.tcptype = elems[i + 1];
|
|
break;
|
|
default: // TODO
|
|
console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
|
|
}
|
|
}
|
|
candidate.network = '1';
|
|
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
|
|
return candidate;
|
|
},
|
|
build_icecandidate: function (cand) {
|
|
var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
|
|
line += ' ';
|
|
switch (cand.type) {
|
|
case 'srflx':
|
|
case 'prflx':
|
|
case 'relay':
|
|
if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
|
|
line += 'raddr';
|
|
line += ' ';
|
|
line += cand['rel-addr'];
|
|
line += ' ';
|
|
line += 'rport';
|
|
line += ' ';
|
|
line += cand['rel-port'];
|
|
line += ' ';
|
|
}
|
|
break;
|
|
}
|
|
if (cand.hasOwnAttribute('tcptype')) {
|
|
line += 'tcptype';
|
|
line += ' ';
|
|
line += cand.tcptype;
|
|
line += ' ';
|
|
}
|
|
line += 'generation';
|
|
line += ' ';
|
|
line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
|
|
return line;
|
|
},
|
|
parse_ssrc: function (desc) {
|
|
// proprietary mapping of a=ssrc lines
|
|
// TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
|
|
// and parse according to that
|
|
var lines = desc.split('\r\n'),
|
|
data = {};
|
|
for (var i = 0; i < lines.length; i++) {
|
|
if (lines[i].substring(0, 7) == 'a=ssrc:') {
|
|
var idx = lines[i].indexOf(' ');
|
|
data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
|
|
}
|
|
}
|
|
return data;
|
|
},
|
|
parse_rtcpfb: function (line) {
|
|
var parts = line.substr(10).split(' ');
|
|
var data = {};
|
|
data.pt = parts.shift();
|
|
data.type = parts.shift();
|
|
data.params = parts;
|
|
return data;
|
|
},
|
|
parse_extmap: function (line) {
|
|
var parts = line.substr(9).split(' ');
|
|
var data = {};
|
|
data.value = parts.shift();
|
|
if (data.value.indexOf('/') != -1) {
|
|
data.direction = data.value.substr(data.value.indexOf('/') + 1);
|
|
data.value = data.value.substr(0, data.value.indexOf('/'));
|
|
} else {
|
|
data.direction = 'both';
|
|
}
|
|
data.uri = parts.shift();
|
|
data.params = parts;
|
|
return data;
|
|
},
|
|
find_line: function (haystack, needle, sessionpart) {
|
|
var lines = haystack.split('\r\n');
|
|
for (var i = 0; i < lines.length; i++) {
|
|
if (lines[i].substring(0, needle.length) == needle) {
|
|
return lines[i];
|
|
}
|
|
}
|
|
if (!sessionpart) {
|
|
return false;
|
|
}
|
|
// search session part
|
|
lines = sessionpart.split('\r\n');
|
|
for (var j = 0; j < lines.length; j++) {
|
|
if (lines[j].substring(0, needle.length) == needle) {
|
|
return lines[j];
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
find_lines: function (haystack, needle, sessionpart) {
|
|
var lines = haystack.split('\r\n'),
|
|
needles = [];
|
|
for (var i = 0; i < lines.length; i++) {
|
|
if (lines[i].substring(0, needle.length) == needle)
|
|
needles.push(lines[i]);
|
|
}
|
|
if (needles.length || !sessionpart) {
|
|
return needles;
|
|
}
|
|
// search session part
|
|
lines = sessionpart.split('\r\n');
|
|
for (var j = 0; j < lines.length; j++) {
|
|
if (lines[j].substring(0, needle.length) == needle) {
|
|
needles.push(lines[j]);
|
|
}
|
|
}
|
|
return needles;
|
|
},
|
|
candidateToJingle: function (line) {
|
|
// a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
|
|
// <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
|
|
if (line.indexOf('candidate:') === 0) {
|
|
line = 'a=' + line;
|
|
} else if (line.substring(0, 12) != 'a=candidate:') {
|
|
console.log('parseCandidate called with a line that is not a candidate line');
|
|
console.log(line);
|
|
return null;
|
|
}
|
|
if (line.substring(line.length - 2) == '\r\n') // chomp it
|
|
line = line.substring(0, line.length - 2);
|
|
var candidate = {},
|
|
elems = line.split(' '),
|
|
i;
|
|
if (elems[6] != 'typ') {
|
|
console.log('did not find typ in the right place');
|
|
console.log(line);
|
|
return null;
|
|
}
|
|
candidate.foundation = elems[0].substring(12);
|
|
candidate.component = elems[1];
|
|
candidate.protocol = elems[2].toLowerCase();
|
|
candidate.priority = elems[3];
|
|
candidate.ip = elems[4];
|
|
candidate.port = elems[5];
|
|
// elems[6] => "typ"
|
|
candidate.type = elems[7];
|
|
|
|
candidate.generation = '0'; // default, may be overwritten below
|
|
for (i = 8; i < elems.length; i += 2) {
|
|
switch (elems[i]) {
|
|
case 'raddr':
|
|
candidate['rel-addr'] = elems[i + 1];
|
|
break;
|
|
case 'rport':
|
|
candidate['rel-port'] = elems[i + 1];
|
|
break;
|
|
case 'generation':
|
|
candidate.generation = elems[i + 1];
|
|
break;
|
|
case 'tcptype':
|
|
candidate.tcptype = elems[i + 1];
|
|
break;
|
|
default: // TODO
|
|
console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
|
|
}
|
|
}
|
|
candidate.network = '1';
|
|
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
|
|
return candidate;
|
|
},
|
|
candidateFromJingle: function (cand) {
|
|
var line = 'a=candidate:';
|
|
line += cand.getAttribute('foundation');
|
|
line += ' ';
|
|
line += cand.getAttribute('component');
|
|
line += ' ';
|
|
line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
|
|
line += ' ';
|
|
line += cand.getAttribute('priority');
|
|
line += ' ';
|
|
line += cand.getAttribute('ip');
|
|
line += ' ';
|
|
line += cand.getAttribute('port');
|
|
line += ' ';
|
|
line += 'typ';
|
|
line += ' ' + cand.getAttribute('type');
|
|
line += ' ';
|
|
switch (cand.getAttribute('type')) {
|
|
case 'srflx':
|
|
case 'prflx':
|
|
case 'relay':
|
|
if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
|
|
line += 'raddr';
|
|
line += ' ';
|
|
line += cand.getAttribute('rel-addr');
|
|
line += ' ';
|
|
line += 'rport';
|
|
line += ' ';
|
|
line += cand.getAttribute('rel-port');
|
|
line += ' ';
|
|
}
|
|
break;
|
|
}
|
|
if (cand.getAttribute('protocol').toLowerCase() == 'tcp') {
|
|
line += 'tcptype';
|
|
line += ' ';
|
|
line += cand.getAttribute('tcptype');
|
|
line += ' ';
|
|
}
|
|
line += 'generation';
|
|
line += ' ';
|
|
line += cand.getAttribute('generation') || '0';
|
|
return line + '\r\n';
|
|
}
|
|
};
|
|
|
|
exports.SDPUtil = SDPUtil;
|
|
|