Source code

Revision control

Copy as Markdown

Other Tools

/* eslint-env node */
"use strict";
// SDP helpers.
var SDPUtils = {};
// Generate an alphanumeric identifier for cname or mids.
// TODO: use UUIDs instead? https://gist.github.com/jed/982883
SDPUtils.generateIdentifier = function() {
return Math.random()
.toString(36)
.substr(2, 10);
};
// The RTCP CNAME used by all peerconnections from the same JS.
SDPUtils.localCName = SDPUtils.generateIdentifier();
// Splits SDP into lines, dealing with both CRLF and LF.
SDPUtils.splitLines = function(blob) {
return blob
.trim()
.split("\n")
.map(function(line) {
return line.trim();
});
};
// Splits SDP into sessionpart and mediasections. Ensures CRLF.
SDPUtils.splitSections = function(blob) {
var parts = blob.split("\nm=");
return parts.map(function(part, index) {
return (index > 0 ? "m=" + part : part).trim() + "\r\n";
});
};
// returns the session description.
SDPUtils.getDescription = function(blob) {
var sections = SDPUtils.splitSections(blob);
return sections && sections[0];
};
// returns the individual media sections.
SDPUtils.getMediaSections = function(blob) {
var sections = SDPUtils.splitSections(blob);
sections.shift();
return sections;
};
// Returns lines that start with a certain prefix.
SDPUtils.matchPrefix = function(blob, prefix) {
return SDPUtils.splitLines(blob).filter(function(line) {
return line.indexOf(prefix) === 0;
});
};
SDPUtils.matchPrefixAndTrim = function(blob, prefix) {
return SDPUtils.matchPrefix(blob, prefix).map(l => l.substr(prefix.length).trim());
}
// Parses an ICE candidate line. Sample input:
// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8
// rport 55996"
SDPUtils.parseCandidate = function(line) {
var parts;
// Parse both variants.
if (line.indexOf("a=candidate:") === 0) {
parts = line.substring(12).split(" ");
} else {
parts = line.substring(10).split(" ");
}
var candidate = {
foundation: parts[0],
component: parseInt(parts[1], 10),
protocol: parts[2].toLowerCase(),
priority: parseInt(parts[3], 10),
ip: parts[4],
address: parts[4], // address is an alias for ip.
port: parseInt(parts[5], 10),
// skip parts[6] == 'typ'
type: parts[7],
};
for (var i = 8; i < parts.length; i += 2) {
switch (parts[i]) {
case "raddr":
candidate.relatedAddress = parts[i + 1];
break;
case "rport":
candidate.relatedPort = parseInt(parts[i + 1], 10);
break;
case "tcptype":
candidate.tcpType = parts[i + 1];
break;
case "ufrag":
candidate.ufrag = parts[i + 1]; // for backward compability.
candidate.usernameFragment = parts[i + 1];
break;
default:
// extension handling, in particular ufrag
candidate[parts[i]] = parts[i + 1];
break;
}
}
return candidate;
};
// Translates a candidate object into SDP candidate attribute.
SDPUtils.writeCandidate = function(candidate) {
var sdp = [];
sdp.push(candidate.foundation);
sdp.push(candidate.component);
sdp.push(candidate.protocol.toUpperCase());
sdp.push(candidate.priority);
sdp.push(candidate.address || candidate.ip);
sdp.push(candidate.port);
var type = candidate.type;
sdp.push("typ");
sdp.push(type);
if (type !== "host" && candidate.relatedAddress && candidate.relatedPort) {
sdp.push("raddr");
sdp.push(candidate.relatedAddress);
sdp.push("rport");
sdp.push(candidate.relatedPort);
}
if (candidate.tcpType && candidate.protocol.toLowerCase() === "tcp") {
sdp.push("tcptype");
sdp.push(candidate.tcpType);
}
if (candidate.usernameFragment || candidate.ufrag) {
sdp.push("ufrag");
sdp.push(candidate.usernameFragment || candidate.ufrag);
}
return "candidate:" + sdp.join(" ");
};
// Parses an ice-options line, returns an array of option tags.
// a=ice-options:foo bar
SDPUtils.parseIceOptions = function(line) {
return line.substr(14).split(" ");
};
// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
// a=rtpmap:111 opus/48000/2
SDPUtils.parseRtpMap = function(line) {
var parts = line.substr(9).split(" ");
var parsed = {
payloadType: parseInt(parts.shift(), 10), // was: id
};
parts = parts[0].split("/");
parsed.name = parts[0];
parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1;
// legacy alias, got renamed back to channels in ORTC.
parsed.numChannels = parsed.channels;
return parsed;
};
// Generate an a=rtpmap line from RTCRtpCodecCapability or
// RTCRtpCodecParameters.
SDPUtils.writeRtpMap = function(codec) {
var pt = codec.payloadType;
if (codec.preferredPayloadType !== undefined) {
pt = codec.preferredPayloadType;
}
var channels = codec.channels || codec.numChannels || 1;
return (
"a=rtpmap:" +
pt +
" " +
codec.name +
"/" +
codec.clockRate +
(channels !== 1 ? "/" + channels : "") +
"\r\n"
);
};
// Parses an a=extmap line (headerextension from RFC 5285). Sample input:
// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset
SDPUtils.parseExtmap = function(line) {
var parts = line.substr(9).split(" ");
return {
id: parseInt(parts[0], 10),
direction: parts[0].indexOf("/") > 0 ? parts[0].split("/")[1] : "sendrecv",
uri: parts[1],
};
};
// Generates a=extmap line from RTCRtpHeaderExtensionParameters or
// RTCRtpHeaderExtension.
SDPUtils.writeExtmap = function(headerExtension) {
return (
"a=extmap:" +
(headerExtension.id || headerExtension.preferredId) +
(headerExtension.direction && headerExtension.direction !== "sendrecv"
? "/" + headerExtension.direction
: "") +
" " +
headerExtension.uri +
"\r\n"
);
};
// Parses an ftmp line, returns dictionary. Sample input:
// a=fmtp:96 vbr=on;cng=on
// Also deals with vbr=on; cng=on
SDPUtils.parseFmtp = function(line) {
var parsed = {};
var kv;
var parts = line.substr(line.indexOf(" ") + 1).split(";");
for (var j = 0; j < parts.length; j++) {
kv = parts[j].trim().split("=");
parsed[kv[0].trim()] = kv[1];
}
return parsed;
};
// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
SDPUtils.writeFmtp = function(codec) {
var line = "";
var pt = codec.payloadType;
if (codec.preferredPayloadType !== undefined) {
pt = codec.preferredPayloadType;
}
if (codec.parameters && Object.keys(codec.parameters).length) {
var params = [];
Object.keys(codec.parameters).forEach(function(param) {
if (codec.parameters[param]) {
params.push(param + "=" + codec.parameters[param]);
} else {
params.push(param);
}
});
line += "a=fmtp:" + pt + " " + params.join(";") + "\r\n";
}
return line;
};
// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
// a=rtcp-fb:98 nack rpsi
SDPUtils.parseRtcpFb = function(line) {
var parts = line.substr(line.indexOf(" ") + 1).split(" ");
return {
type: parts.shift(),
parameter: parts.join(" "),
};
};
// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
SDPUtils.writeRtcpFb = function(codec) {
var lines = "";
var pt = codec.payloadType;
if (codec.preferredPayloadType !== undefined) {
pt = codec.preferredPayloadType;
}
if (codec.rtcpFeedback && codec.rtcpFeedback.length) {
// FIXME: special handling for trr-int?
codec.rtcpFeedback.forEach(function(fb) {
lines +=
"a=rtcp-fb:" +
pt +
" " +
fb.type +
(fb.parameter && fb.parameter.length ? " " + fb.parameter : "") +
"\r\n";
});
}
return lines;
};
// Parses an RFC 5576 ssrc media attribute. Sample input:
// a=ssrc:3735928559 cname:something
SDPUtils.parseSsrcMedia = function(line) {
var sp = line.indexOf(" ");
var parts = {
ssrc: parseInt(line.substr(7, sp - 7), 10),
};
var colon = line.indexOf(":", sp);
if (colon > -1) {
parts.attribute = line.substr(sp + 1, colon - sp - 1);
parts.value = line.substr(colon + 1);
} else {
parts.attribute = line.substr(sp + 1);
}
return parts;
};
SDPUtils.parseSsrcGroup = function(line) {
var parts = line.substr(13).split(" ");
return {
semantics: parts.shift(),
ssrcs: parts.map(function(ssrc) {
return parseInt(ssrc, 10);
}),
};
};
// Extracts the MID (RFC 5888) from a media section.
// returns the MID or undefined if no mid line was found.
SDPUtils.getMid = function(mediaSection) {
var mid = SDPUtils.matchPrefix(mediaSection, "a=mid:")[0];
if (mid) {
return mid.substr(6);
}
};
SDPUtils.parseFingerprint = function(line) {
var parts = line.substr(14).split(" ");
return {
algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge.
value: parts[1],
};
};
// Extracts DTLS parameters from SDP media section or sessionpart.
// FIXME: for consistency with other functions this should only
// get the fingerprint line as input. See also getIceParameters.
SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {
var lines = SDPUtils.matchPrefix(
mediaSection + sessionpart,
"a=fingerprint:"
);
// Note: a=setup line is ignored since we use the 'auto' role.
// Note2: 'algorithm' is not case sensitive except in Edge.
return {
role: "auto",
fingerprints: lines.map(SDPUtils.parseFingerprint),
};
};
// Serializes DTLS parameters to SDP.
SDPUtils.writeDtlsParameters = function(params, setupType) {
var sdp = "a=setup:" + setupType + "\r\n";
params.fingerprints.forEach(function(fp) {
sdp += "a=fingerprint:" + fp.algorithm + " " + fp.value + "\r\n";
});
return sdp;
};
// Parses a=crypto lines into
SDPUtils.parseCryptoLine = function(line) {
var parts = line.substr(9).split(" ");
return {
tag: parseInt(parts[0], 10),
cryptoSuite: parts[1],
keyParams: parts[2],
sessionParams: parts.slice(3),
};
};
SDPUtils.writeCryptoLine = function(parameters) {
return (
"a=crypto:" +
parameters.tag +
" " +
parameters.cryptoSuite +
" " +
(typeof parameters.keyParams === "object"
? SDPUtils.writeCryptoKeyParams(parameters.keyParams)
: parameters.keyParams) +
(parameters.sessionParams ? " " + parameters.sessionParams.join(" ") : "") +
"\r\n"
);
};
// Parses the crypto key parameters into
SDPUtils.parseCryptoKeyParams = function(keyParams) {
if (keyParams.indexOf("inline:") !== 0) {
return null;
}
var parts = keyParams.substr(7).split("|");
return {
keyMethod: "inline",
keySalt: parts[0],
lifeTime: parts[1],
mkiValue: parts[2] ? parts[2].split(":")[0] : undefined,
mkiLength: parts[2] ? parts[2].split(":")[1] : undefined,
};
};
SDPUtils.writeCryptoKeyParams = function(keyParams) {
return (
keyParams.keyMethod +
":" +
keyParams.keySalt +
(keyParams.lifeTime ? "|" + keyParams.lifeTime : "") +
(keyParams.mkiValue && keyParams.mkiLength
? "|" + keyParams.mkiValue + ":" + keyParams.mkiLength
: "")
);
};
// Extracts all SDES paramters.
SDPUtils.getCryptoParameters = function(mediaSection, sessionpart) {
var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, "a=crypto:");
return lines.map(SDPUtils.parseCryptoLine);
};
// Parses ICE information from SDP media section or sessionpart.
// FIXME: for consistency with other functions this should only
// get the ice-ufrag and ice-pwd lines as input.
SDPUtils.getIceParameters = function(mediaSection, sessionpart) {
var ufrag = SDPUtils.matchPrefix(
mediaSection + sessionpart,
"a=ice-ufrag:"
)[0];
var pwd = SDPUtils.matchPrefix(mediaSection + sessionpart, "a=ice-pwd:")[0];
if (!(ufrag && pwd)) {
return null;
}
return {
usernameFragment: ufrag.substr(12),
password: pwd.substr(10),
};
};
// Serializes ICE parameters to SDP.
SDPUtils.writeIceParameters = function(params) {
return (
"a=ice-ufrag:" +
params.usernameFragment +
"\r\n" +
"a=ice-pwd:" +
params.password +
"\r\n"
);
};
// Parses the SDP media section and returns RTCRtpParameters.
SDPUtils.parseRtpParameters = function(mediaSection) {
var description = {
codecs: [],
headerExtensions: [],
fecMechanisms: [],
rtcp: [],
};
var lines = SDPUtils.splitLines(mediaSection);
var mline = lines[0].split(" ");
for (var i = 3; i < mline.length; i++) {
// find all codecs from mline[3..]
var pt = mline[i];
var rtpmapline = SDPUtils.matchPrefix(
mediaSection,
"a=rtpmap:" + pt + " "
)[0];
if (rtpmapline) {
var codec = SDPUtils.parseRtpMap(rtpmapline);
var fmtps = SDPUtils.matchPrefix(mediaSection, "a=fmtp:" + pt + " ");
// Only the first a=fmtp:<pt> is considered.
codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};
codec.rtcpFeedback = SDPUtils.matchPrefix(
mediaSection,
"a=rtcp-fb:" + pt + " "
).map(SDPUtils.parseRtcpFb);
description.codecs.push(codec);
// parse FEC mechanisms from rtpmap lines.
switch (codec.name.toUpperCase()) {
case "RED":
case "ULPFEC":
description.fecMechanisms.push(codec.name.toUpperCase());
break;
default:
// only RED and ULPFEC are recognized as FEC mechanisms.
break;
}
}
}
SDPUtils.matchPrefix(mediaSection, "a=extmap:").forEach(function(line) {
description.headerExtensions.push(SDPUtils.parseExtmap(line));
});
// FIXME: parse rtcp.
return description;
};
// Generates parts of the SDP media section describing the capabilities /
// parameters.
SDPUtils.writeRtpDescription = function(kind, caps) {
var sdp = "";
// Build the mline.
sdp += "m=" + kind + " ";
sdp += caps.codecs.length > 0 ? "9" : "0"; // reject if no codecs.
sdp += " UDP/TLS/RTP/SAVPF ";
sdp +=
caps.codecs
.map(function(codec) {
if (codec.preferredPayloadType !== undefined) {
return codec.preferredPayloadType;
}
return codec.payloadType;
})
.join(" ") + "\r\n";
sdp += "c=IN IP4 0.0.0.0\r\n";
sdp += "a=rtcp:9 IN IP4 0.0.0.0\r\n";
// Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
caps.codecs.forEach(function(codec) {
sdp += SDPUtils.writeRtpMap(codec);
sdp += SDPUtils.writeFmtp(codec);
sdp += SDPUtils.writeRtcpFb(codec);
});
var maxptime = 0;
caps.codecs.forEach(function(codec) {
if (codec.maxptime > maxptime) {
maxptime = codec.maxptime;
}
});
if (maxptime > 0) {
sdp += "a=maxptime:" + maxptime + "\r\n";
}
sdp += "a=rtcp-mux\r\n";
if (caps.headerExtensions) {
caps.headerExtensions.forEach(function(extension) {
sdp += SDPUtils.writeExtmap(extension);
});
}
// FIXME: write fecMechanisms.
return sdp;
};
// Parses the SDP media section and returns an array of
// RTCRtpEncodingParameters.
SDPUtils.parseRtpEncodingParameters = function(mediaSection) {
var encodingParameters = [];
var description = SDPUtils.parseRtpParameters(mediaSection);
var hasRed = description.fecMechanisms.indexOf("RED") !== -1;
var hasUlpfec = description.fecMechanisms.indexOf("ULPFEC") !== -1;
// filter a=ssrc:... cname:, ignore PlanB-msid
var ssrcs = SDPUtils.matchPrefix(mediaSection, "a=ssrc:")
.map(function(line) {
return SDPUtils.parseSsrcMedia(line);
})
.filter(function(parts) {
return parts.attribute === "cname";
});
var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc;
var secondarySsrc;
var flows = SDPUtils.matchPrefix(mediaSection, "a=ssrc-group:FID").map(
function(line) {
var parts = line.substr(17).split(" ");
return parts.map(function(part) {
return parseInt(part, 10);
});
}
);
if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) {
secondarySsrc = flows[0][1];
}
description.codecs.forEach(function(codec) {
if (codec.name.toUpperCase() === "RTX" && codec.parameters.apt) {
var encParam = {
ssrc: primarySsrc,
codecPayloadType: parseInt(codec.parameters.apt, 10),
};
if (primarySsrc && secondarySsrc) {
encParam.rtx = { ssrc: secondarySsrc };
}
encodingParameters.push(encParam);
if (hasRed) {
encParam = JSON.parse(JSON.stringify(encParam));
encParam.fec = {
ssrc: primarySsrc,
mechanism: hasUlpfec ? "red+ulpfec" : "red",
};
encodingParameters.push(encParam);
}
}
});
if (encodingParameters.length === 0 && primarySsrc) {
encodingParameters.push({
ssrc: primarySsrc,
});
}
// we support both b=AS and b=TIAS but interpret AS as TIAS.
var bandwidth = SDPUtils.matchPrefix(mediaSection, "b=");
if (bandwidth.length) {
if (bandwidth[0].indexOf("b=TIAS:") === 0) {
bandwidth = parseInt(bandwidth[0].substr(7), 10);
} else if (bandwidth[0].indexOf("b=AS:") === 0) {
// use formula from JSEP to convert b=AS to TIAS value.
bandwidth =
parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95 - 50 * 40 * 8;
} else {
bandwidth = undefined;
}
encodingParameters.forEach(function(params) {
params.maxBitrate = bandwidth;
});
}
return encodingParameters;
};
SDPUtils.parseRtcpParameters = function(mediaSection) {
var rtcpParameters = {};
// Gets the first SSRC. Note tha with RTX there might be multiple
// SSRCs.
var remoteSsrc = SDPUtils.matchPrefix(mediaSection, "a=ssrc:")
.map(function(line) {
return SDPUtils.parseSsrcMedia(line);
})
.filter(function(obj) {
return obj.attribute === "cname";
})[0];
if (remoteSsrc) {
rtcpParameters.cname = remoteSsrc.value;
rtcpParameters.ssrc = remoteSsrc.ssrc;
}
// Edge uses the compound attribute instead of reducedSize
// compound is !reducedSize
var rsize = SDPUtils.matchPrefix(mediaSection, "a=rtcp-rsize");
rtcpParameters.reducedSize = rsize.length > 0;
rtcpParameters.compound = rsize.length === 0;
// parses the rtcp-mux attrÑ–bute.
// Note that Edge does not support unmuxed RTCP.
var mux = SDPUtils.matchPrefix(mediaSection, "a=rtcp-mux");
rtcpParameters.mux = mux.length > 0;
return rtcpParameters;
};
// parses either a=msid: or a=ssrc:... msid lines and returns
// the id of the MediaStream and MediaStreamTrack.
SDPUtils.parseMsid = function(mediaSection) {
var parts;
var spec = SDPUtils.matchPrefix(mediaSection, "a=msid:");
if (spec.length === 1) {
parts = spec[0].substr(7).split(" ");
return { stream: parts[0], track: parts[1] };
}
var planB = SDPUtils.matchPrefix(mediaSection, "a=ssrc:")
.map(function(line) {
return SDPUtils.parseSsrcMedia(line);
})
.filter(function(msidParts) {
return msidParts.attribute === "msid";
});
if (planB.length > 0) {
parts = planB[0].value.split(" ");
return { stream: parts[0], track: parts[1] };
}
};
// SCTP
// parses draft-ietf-mmusic-sctp-sdp-26 first and falls back
// to draft-ietf-mmusic-sctp-sdp-05
SDPUtils.parseSctpDescription = function(mediaSection) {
var mline = SDPUtils.parseMLine(mediaSection);
var maxSizeLine = SDPUtils.matchPrefix(mediaSection, "a=max-message-size:");
var maxMessageSize;
if (maxSizeLine.length > 0) {
maxMessageSize = parseInt(maxSizeLine[0].substr(19), 10);
}
if (isNaN(maxMessageSize)) {
maxMessageSize = 65536;
}
var sctpPort = SDPUtils.matchPrefix(mediaSection, "a=sctp-port:");
if (sctpPort.length > 0) {
return {
port: parseInt(sctpPort[0].substr(12), 10),
protocol: mline.fmt,
maxMessageSize,
};
}
var sctpMapLines = SDPUtils.matchPrefix(mediaSection, "a=sctpmap:");
if (sctpMapLines.length > 0) {
var parts = SDPUtils.matchPrefix(mediaSection, "a=sctpmap:")[0]
.substr(10)
.split(" ");
return {
port: parseInt(parts[0], 10),
protocol: parts[1],
maxMessageSize,
};
}
};
// SCTP
// outputs the draft-ietf-mmusic-sctp-sdp-26 version that all browsers
// support by now receiving in this format, unless we originally parsed
// as the draft-ietf-mmusic-sctp-sdp-05 format (indicated by the m-line
// protocol of DTLS/SCTP -- without UDP/ or TCP/)
SDPUtils.writeSctpDescription = function(media, sctp) {
var output = [];
if (media.protocol !== "DTLS/SCTP") {
output = [
"m=" + media.kind + " 9 " + media.protocol + " " + sctp.protocol + "\r\n",
"c=IN IP4 0.0.0.0\r\n",
"a=sctp-port:" + sctp.port + "\r\n",
];
} else {
output = [
"m=" + media.kind + " 9 " + media.protocol + " " + sctp.port + "\r\n",
"c=IN IP4 0.0.0.0\r\n",
"a=sctpmap:" + sctp.port + " " + sctp.protocol + " 65535\r\n",
];
}
if (sctp.maxMessageSize !== undefined) {
output.push("a=max-message-size:" + sctp.maxMessageSize + "\r\n");
}
return output.join("");
};
// Generate a session ID for SDP.
// recommends using a cryptographically random +ve 64-bit value
// but right now this should be acceptable and within the right range
SDPUtils.generateSessionId = function() {
return Math.floor((Math.random() * 4294967296) + 1);
};
// Write boilder plate for start of SDP
// sessId argument is optional - if not supplied it will
// be generated randomly
// sessVersion is optional and defaults to 2
// sessUser is optional and defaults to 'thisisadapterortc'
SDPUtils.writeSessionBoilerplate = function(sessId, sessVer, sessUser) {
var sessionId;
var version = sessVer !== undefined ? sessVer : 2;
if (sessId) {
sessionId = sessId;
} else {
sessionId = SDPUtils.generateSessionId();
}
var user = sessUser || "thisisadapterortc";
// FIXME: sess-id should be an NTP timestamp.
return (
"v=0\r\n" +
"o=" +
user +
" " +
sessionId +
" " +
version +
" IN IP4 127.0.0.1\r\n" +
"s=-\r\n" +
"t=0 0\r\n"
);
};
SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) {
var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
// Map ICE parameters (ufrag, pwd) to SDP.
sdp += SDPUtils.writeIceParameters(
transceiver.iceGatherer.getLocalParameters()
);
// Map DTLS parameters to SDP.
sdp += SDPUtils.writeDtlsParameters(
transceiver.dtlsTransport.getLocalParameters(),
type === "offer" ? "actpass" : "active"
);
sdp += "a=mid:" + transceiver.mid + "\r\n";
if (transceiver.direction) {
sdp += "a=" + transceiver.direction + "\r\n";
} else if (transceiver.rtpSender && transceiver.rtpReceiver) {
sdp += "a=sendrecv\r\n";
} else if (transceiver.rtpSender) {
sdp += "a=sendonly\r\n";
} else if (transceiver.rtpReceiver) {
sdp += "a=recvonly\r\n";
} else {
sdp += "a=inactive\r\n";
}
if (transceiver.rtpSender) {
// spec.
var msid =
"msid:" + stream.id + " " + transceiver.rtpSender.track.id + "\r\n";
sdp += "a=" + msid;
// for Chrome.
sdp += "a=ssrc:" + transceiver.sendEncodingParameters[0].ssrc + " " + msid;
if (transceiver.sendEncodingParameters[0].rtx) {
sdp +=
"a=ssrc:" + transceiver.sendEncodingParameters[0].rtx.ssrc + " " + msid;
sdp +=
"a=ssrc-group:FID " +
transceiver.sendEncodingParameters[0].ssrc +
" " +
transceiver.sendEncodingParameters[0].rtx.ssrc +
"\r\n";
}
}
// FIXME: this should be written by writeRtpDescription.
sdp +=
"a=ssrc:" +
transceiver.sendEncodingParameters[0].ssrc +
" cname:" +
SDPUtils.localCName +
"\r\n";
if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
sdp +=
"a=ssrc:" +
transceiver.sendEncodingParameters[0].rtx.ssrc +
" cname:" +
SDPUtils.localCName +
"\r\n";
}
return sdp;
};
// Gets the direction from the mediaSection or the sessionpart.
SDPUtils.getDirection = function(mediaSection, sessionpart) {
// Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
var lines = SDPUtils.splitLines(mediaSection);
for (var i = 0; i < lines.length; i++) {
switch (lines[i]) {
case "a=sendrecv":
case "a=sendonly":
case "a=recvonly":
case "a=inactive":
return lines[i].substr(2);
default:
// FIXME: What should happen here?
}
}
if (sessionpart) {
return SDPUtils.getDirection(sessionpart);
}
return "sendrecv";
};
SDPUtils.getKind = function(mediaSection) {
var lines = SDPUtils.splitLines(mediaSection);
var mline = lines[0].split(" ");
return mline[0].substr(2);
};
SDPUtils.isRejected = function(mediaSection) {
return mediaSection.split(" ", 2)[1] === "0";
};
SDPUtils.parseMLine = function(mediaSection) {
var lines = SDPUtils.splitLines(mediaSection);
var parts = lines[0].substr(2).split(" ");
return {
kind: parts[0],
port: parseInt(parts[1], 10),
protocol: parts[2],
fmt: parts.slice(3).join(" "),
};
};
SDPUtils.parseOLine = function(mediaSection) {
var line = SDPUtils.matchPrefix(mediaSection, "o=")[0];
var parts = line.substr(2).split(" ");
return {
username: parts[0],
sessionId: parts[1],
sessionVersion: parseInt(parts[2], 10),
netType: parts[3],
addressType: parts[4],
address: parts[5],
};
};
// a very naive interpretation of a valid SDP.
SDPUtils.isValidSDP = function(blob) {
if (typeof blob !== "string" || blob.length === 0) {
return false;
}
var lines = SDPUtils.splitLines(blob);
for (var i = 0; i < lines.length; i++) {
if (lines[i].length < 2 || lines[i].charAt(1) !== "=") {
return false;
}
// TODO: check the modifier a bit more.
}
return true;
};
// Expose public methods.
if (typeof module === "object") {
module.exports = SDPUtils;
}