/* 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; }); }; // 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 // https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#dictionary-rtcsrtpsdesparameters-members 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 // https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#rtcsrtpkeyparam* 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: 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; }; // parses http://draft.ortc.org/#rtcrtcpparameters* 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. // https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1 // 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; }