| /* |
| * Copyright (C) 2014-2015 Ericsson AB. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| "use strict"; |
| |
| if (typeof(SDP) == "undefined") |
| var SDP = {}; |
| |
| (function () { |
| var regexps = { |
| "vline": "^v=([\\d]+).*$", |
| "oline": "^o=([\\w\\-@\\.]+) ([\\d]+) ([\\d]+) IN (IP[46]) ([\\d\\.a-f\\:]+).*$", |
| "sline": "^s=(.*)$", |
| "tline": "^t=([\\d]+) ([\\d]+).*$", |
| "cline": "^c=IN (IP[46]) ([\\d\\.a-f\\:]+).*$", |
| "msidsemantic": "^a=msid-semantic: *WMS .*$", |
| "mblock": "^m=(audio|video|application) ([\\d]+) ([A-Z/]+)([\\d ]*)$\\r?\\n", |
| "mode": "^a=(sendrecv|sendonly|recvonly|inactive).*$", |
| "mid": "^a=mid:([!#$%&'*+-.\\w]*).*$", |
| "rtpmap": "^a=rtpmap:${type} ([\\w\\-]+)/([\\d]+)/?([\\d]+)?.*$", |
| "fmtp": "^a=fmtp:${type} ([\\w\\-=; ]+).*$", |
| "param": "([\\w\\-]+)=([\\w\\-]+);?", |
| "nack": "^a=rtcp-fb:${type} nack$", |
| "nackpli": "^a=rtcp-fb:${type} nack pli$", |
| "ccmfir": "^a=rtcp-fb:${type} ccm fir$", |
| "ericscream": "^a=rtcp-fb:${type} ericscream$", |
| "rtcp": "^a=rtcp:([\\d]+)( IN (IP[46]) ([\\d\\.a-f\\:]+))?.*$", |
| "rtcpmux": "^a=rtcp-mux.*$", |
| "cname": "^a=ssrc:(\\d+) cname:([\\w+/\\-@\\.\\{\\}]+).*$", |
| "msid": "^a=(ssrc:\\d+ )?msid:([\\w+/\\-=]+) +([\\w+/\\-=]+).*$", |
| "ufrag": "^a=ice-ufrag:([\\w+/]*).*$", |
| "pwd": "^a=ice-pwd:([\\w+/]*).*$", |
| "candidate": "^a=candidate:(\\d+) (\\d) (UDP|TCP) ([\\d\\.]*) ([\\d\\.a-f\\:]*) (\\d*)" + |
| " typ ([a-z]*)( raddr ([\\d\\.a-f\\:]*) rport (\\d*))?" + |
| "( tcptype (active|passive|so))?.*$", |
| "fingerprint": "^a=fingerprint:(sha-1|sha-256) ([A-Fa-f\\d\:]+).*$", |
| "setup": "^a=setup:(actpass|active|passive).*$", |
| "sctpmap": "^a=sctpmap:${port} ([\\w\\-]+)( [\\d]+)?.*$" |
| }; |
| |
| var templates = { |
| "sdp": |
| "v=${version}\r\n" + |
| "o=${username} ${sessionId} ${sessionVersion} ${netType} ${addressType} ${address}\r\n" + |
| "s=${sessionName}\r\n" + |
| "t=${startTime} ${stopTime}\r\n" + |
| "${bundleLine}" + |
| "${msidsemanticLine}", |
| |
| "msidsemantic": "a=msid-semantic:WMS ${mediaStreamIds}\r\n", |
| |
| "mblock": |
| "m=${type} ${port} ${protocol} ${fmt}\r\n" + |
| "c=${netType} ${addressType} ${address}\r\n" + |
| "${rtcpLine}" + |
| "${rtcpMuxLine}" + |
| "${bundleOnlyLine}" + |
| "a=${mode}\r\n" + |
| "${midLine}" + |
| "${rtpMapLines}" + |
| "${fmtpLines}" + |
| "${nackLines}" + |
| "${nackpliLines}" + |
| "${ccmfirLines}" + |
| "${ericScreamLines}" + |
| "${cnameLines}" + |
| "${msidLines}" + |
| "${iceCredentialLines}" + |
| "${candidateLines}" + |
| "${dtlsFingerprintLine}" + |
| "${dtlsSetupLine}" + |
| "${sctpmapLine}", |
| |
| "rtcp": "a=rtcp:${port}${[ ]netType}${[ ]addressType}${[ ]address}\r\n", |
| "rtcpMux": "a=rtcp-mux\r\n", |
| "mid": "a=mid:${mid}\r\n", |
| "bundle": "a=group:BUNDLE ${midsBundle}\r\n", |
| |
| "rtpMap": "a=rtpmap:${type} ${encodingName}/${clockRate}${[/]channels}\r\n", |
| "fmtp": "a=fmtp:${type} ${parameters}\r\n", |
| "nack": "a=rtcp-fb:${type} nack\r\n", |
| "nackpli": "a=rtcp-fb:${type} nack pli\r\n", |
| "ccmfir": "a=rtcp-fb:${type} ccm fir\r\n", |
| "ericscream": "a=rtcp-fb:${type} ericscream\r\n", |
| |
| "cname": "a=ssrc:${ssrc} cname:${cname}\r\n", |
| "msid": "a=msid:${mediaStreamId} ${mediaStreamTrackId}\r\n", |
| |
| "iceCredentials": |
| "a=ice-ufrag:${ufrag}\r\n" + |
| "a=ice-pwd:${password}\r\n", |
| |
| "candidate": |
| "a=candidate:${foundation} ${componentId} ${transport} ${priority} ${address} ${port}" + |
| " typ ${type}${[ raddr ]relatedAddress}${[ rport ]relatedPort}${[ tcptype ]tcpType}\r\n", |
| |
| "dtlsFingerprint": "a=fingerprint:${fingerprintHashFunction} ${fingerprint}\r\n", |
| "dtlsSetup": "a=setup:${setup}\r\n", |
| |
| "sctpmap": "a=sctpmap:${port} ${app}${[ ]streams}\r\n" |
| }; |
| |
| function match(data, pattern, flags, alt) { |
| var r = new RegExp(pattern, flags); |
| return data.match(r) || alt && alt.match(r) || null; |
| } |
| |
| function addDefaults(obj, defaults) { |
| for (var p in defaults) { |
| if (!defaults.hasOwnProperty(p)) |
| continue; |
| if (typeof(obj[p]) == "undefined") |
| obj[p] = defaults[p]; |
| } |
| } |
| |
| function fillTemplate(template, info) { |
| var text = template; |
| for (var p in info) { |
| if (!info.hasOwnProperty(p)) |
| continue; |
| var r = new RegExp("\\${(\\[[^\\]]+\\])?" + p + "(\\[[^\\]]+\\])?}"); |
| text = text.replace(r, function (_, prefix, suffix) { |
| if (!info[p] && info[p] != 0) |
| return ""; |
| prefix = prefix ? prefix.substr(1, prefix.length - 2) : ""; |
| suffix = suffix ? suffix.substr(1, suffix.length - 2) : ""; |
| return prefix + info[p] + suffix; |
| }); |
| } |
| return text; |
| } |
| |
| SDP.parse = function (sdpText) { |
| sdpText = new String(sdpText); |
| var sdpObj = {}; |
| var parts = sdpText.split(new RegExp(regexps.mblock, "m")) || [sdpText]; |
| var sblock = parts.shift(); |
| var version = parseInt((match(sblock, regexps.vline, "m") || [])[1]); |
| if (!isNaN(version)) |
| sdpObj.version = version; |
| var originator = match(sblock, regexps.oline, "m");; |
| if (originator) { |
| sdpObj.originator = { |
| "username": originator[1], |
| "sessionId": originator[2], |
| "sessionVersion": parseInt(originator[3]), |
| "netType": "IN", |
| "addressType": originator[4], |
| "address": originator[5] |
| }; |
| } |
| var sessionName = match(sblock, regexps.sline, "m"); |
| if (sessionName) |
| sdpObj.sessionName = sessionName[1]; |
| var sessionTime = match(sblock, regexps.tline, "m"); |
| if (sessionTime) { |
| sdpObj.startTime = parseInt(sessionTime[1]); |
| sdpObj.stopTime = parseInt(sessionTime[2]); |
| } |
| var hasMediaStreamId = !!match(sblock, regexps.msidsemantic, "m"); |
| sdpObj.mediaDescriptions = []; |
| |
| for (var i = 0; i < parts.length; i += 5) { |
| var mediaDescription = { |
| "type": parts[i], |
| "port": parseInt(parts[i + 1]), |
| "protocol": parts[i + 2], |
| }; |
| var fmt = parts[i + 3].replace(/^[\s\uFEFF\xA0]+/, '') |
| .split(/ +/) |
| .map(function (x) { |
| return parseInt(x); |
| }); |
| var mblock = parts[i + 4]; |
| |
| var connection = match(mblock, regexps.cline, "m", sblock); |
| if (connection) { |
| mediaDescription.netType = "IN"; |
| mediaDescription.addressType = connection[1]; |
| mediaDescription.address = connection[2]; |
| } |
| var mode = match(mblock, regexps.mode, "m", sblock); |
| if (mode) |
| mediaDescription.mode = mode[1]; |
| |
| var mid = match(mblock, regexps.mid, "m", sblock); |
| if (mid) |
| mediaDescription.mid = mid[1]; |
| |
| var payloadTypes = []; |
| if (match(mediaDescription.protocol, "(UDP/TLS)?RTP/S?AVPF?")) { |
| mediaDescription.payloads = []; |
| payloadTypes = fmt; |
| } |
| payloadTypes.forEach(function (payloadType) { |
| var payload = { "type": payloadType }; |
| var rtpmapLine = fillTemplate(regexps.rtpmap, payload); |
| var rtpmap = match(mblock, rtpmapLine, "m"); |
| if (rtpmap) { |
| payload.encodingName = rtpmap[1]; |
| payload.clockRate = parseInt(rtpmap[2]); |
| if (mediaDescription.type == "audio") |
| payload.channels = parseInt(rtpmap[3]) || 1; |
| else if (mediaDescription.type == "video") { |
| var nackLine = fillTemplate(regexps.nack, payload); |
| payload.nack = !!match(mblock, nackLine, "m"); |
| var nackpliLine = fillTemplate(regexps.nackpli, payload); |
| payload.nackpli = !!match(mblock, nackpliLine, "m"); |
| var ccmfirLine = fillTemplate(regexps.ccmfir, payload); |
| payload.ccmfir = !!match(mblock, ccmfirLine, "m"); |
| var ericScreamLine = fillTemplate(regexps.ericscream, payload); |
| payload.ericscream = !!match(mblock, ericScreamLine, "m"); |
| } |
| } else if (payloadType == 0 || payloadType == 8) { |
| payload.encodingName = payloadType == 8 ? "PCMA" : "PCMU"; |
| payload.clockRate = 8000; |
| payload.channels = 1; |
| } |
| var fmtpLine = fillTemplate(regexps.fmtp, payload); |
| var fmtp = match(mblock, fmtpLine, "m"); |
| if (fmtp) { |
| payload.parameters = {}; |
| fmtp[1].replace(new RegExp(regexps.param, "g"), |
| function(_, key, value) { |
| key = key.replace(/-([a-z])/g, function (_, c) { |
| return c.toUpperCase(); |
| }); |
| payload.parameters[key] = isNaN(+value) ? value : +value; |
| }); |
| } |
| mediaDescription.payloads.push(payload); |
| }); |
| |
| var rtcp = match(mblock, regexps.rtcp, "m"); |
| if (rtcp) { |
| mediaDescription.rtcp = { |
| "netType": "IN", |
| "port": parseInt(rtcp[1]) |
| }; |
| if (rtcp[2]) { |
| mediaDescription.rtcp.addressType = rtcp[3]; |
| mediaDescription.rtcp.address = rtcp[4]; |
| } |
| } |
| var rtcpmux = match(mblock, regexps.rtcpmux, "m", sblock); |
| if (rtcpmux) { |
| if (!mediaDescription.rtcp) |
| mediaDescription.rtcp = {}; |
| mediaDescription.rtcp.mux = true; |
| } |
| |
| var cnameLines = match(mblock, regexps.cname, "mg"); |
| if (cnameLines) { |
| mediaDescription.ssrcs = []; |
| cnameLines.forEach(function (line) { |
| var cname = match(line, regexps.cname, "m"); |
| mediaDescription.ssrcs.push(parseInt(cname[1])); |
| if (!mediaDescription.cname) |
| mediaDescription.cname = cname[2]; |
| }); |
| } |
| |
| if (hasMediaStreamId) { |
| var msid = match(mblock, regexps.msid, "m"); |
| if (msid) { |
| mediaDescription.mediaStreamId = msid[2]; |
| mediaDescription.mediaStreamTrackId = msid[3]; |
| } |
| } |
| |
| var ufrag = match(mblock, regexps.ufrag, "m", sblock); |
| var pwd = match(mblock, regexps.pwd, "m", sblock); |
| if (ufrag && pwd) { |
| mediaDescription.ice = { |
| "ufrag": ufrag[1], |
| "password": pwd[1] |
| }; |
| } |
| var candidateLines = match(mblock, regexps.candidate, "mig"); |
| if (candidateLines) { |
| if (!mediaDescription.ice) |
| mediaDescription.ice = {}; |
| mediaDescription.ice.candidates = []; |
| candidateLines.forEach(function (line) { |
| var candidateLine = match(line, regexps.candidate, "mi"); |
| var candidate = { |
| "foundation": candidateLine[1], |
| "componentId": parseInt(candidateLine[2]), |
| "transport": candidateLine[3].toUpperCase(), |
| "priority": parseInt(candidateLine[4]), |
| "address": candidateLine[5], |
| "port": parseInt(candidateLine[6]), |
| "type": candidateLine[7] |
| }; |
| if (candidateLine[9]) |
| candidate.relatedAddress = candidateLine[9]; |
| if (!isNaN(candidateLine[10])) |
| candidate.relatedPort = parseInt(candidateLine[10]); |
| if (candidateLine[12]) |
| candidate.tcpType = candidateLine[12]; |
| else if (candidate.transport == "TCP") { |
| if (candidate.port == 0 || candidate.port == 9) { |
| candidate.tcpType = "active"; |
| candidate.port = 9; |
| } else { |
| return; |
| } |
| } |
| mediaDescription.ice.candidates.push(candidate); |
| }); |
| } |
| |
| var fingerprint = match(mblock, regexps.fingerprint, "mi", sblock); |
| if (fingerprint) { |
| mediaDescription.dtls = { |
| "fingerprintHashFunction": fingerprint[1].toLowerCase(), |
| "fingerprint": fingerprint[2].toUpperCase() |
| }; |
| } |
| var setup = match(mblock, regexps.setup, "m", sblock); |
| if (setup) { |
| if (!mediaDescription.dtls) |
| mediaDescription.dtls = {}; |
| mediaDescription.dtls.setup = setup[1]; |
| } |
| |
| if (mediaDescription.protocol == "DTLS/SCTP") { |
| mediaDescription.sctp = { |
| "port": fmt[0] |
| }; |
| var sctpmapLine = fillTemplate(regexps.sctpmap, mediaDescription.sctp); |
| var sctpmap = match(mblock, sctpmapLine, "m"); |
| if (sctpmap) { |
| mediaDescription.sctp.app = sctpmap[1]; |
| if (sctpmap[2]) |
| mediaDescription.sctp.streams = parseInt(sctpmap[2]); |
| } |
| } |
| |
| sdpObj.mediaDescriptions.push(mediaDescription); |
| } |
| |
| return sdpObj; |
| }; |
| |
| SDP.generate = function (sdpObj) { |
| sdpObj = JSON.parse(JSON.stringify(sdpObj)); |
| addDefaults(sdpObj, { |
| "version": 0, |
| "originator": {}, |
| "sessionName": "-", |
| "startTime": 0, |
| "stopTime": 0, |
| "bundlePolicy": "balanced", |
| "mediaDescriptions": [] |
| }); |
| addDefaults(sdpObj.originator, { |
| "username": "-", |
| "sessionId": "" + Math.floor((Math.random() + +new Date()) * 1e6), |
| "sessionVersion": 1, |
| "netType": "IN", |
| "addressType": "IP4", |
| "address": "127.0.0.1" |
| }); |
| var sdpText = fillTemplate(templates.sdp, sdpObj); |
| sdpText = fillTemplate(sdpText, sdpObj.originator); |
| |
| var midsBundle = []; |
| var mediatypesBundle = []; |
| var msidsemanticLine = ""; |
| var mediaStreamIds = []; |
| sdpObj.mediaDescriptions.forEach(function (mdesc) { |
| if (mdesc.mediaStreamId && mdesc.mediaStreamTrackId |
| && mediaStreamIds.indexOf(mdesc.mediaStreamId) == -1) |
| mediaStreamIds.push(mdesc.mediaStreamId); |
| }); |
| if (mediaStreamIds.length) { |
| var msidsemanticLine = fillTemplate(templates.msidsemantic, |
| { "mediaStreamIds": mediaStreamIds.join(" ") }); |
| } |
| sdpText = fillTemplate(sdpText, { "msidsemanticLine": msidsemanticLine }); |
| |
| sdpObj.mediaDescriptions.forEach(function (mediaDescription) { |
| addDefaults(mediaDescription, { |
| "port": 9, |
| "protocol": "UDP/TLS/RTP/SAVPF", |
| "netType": "IN", |
| "addressType": "IP4", |
| "address": "0.0.0.0", |
| "mode": "sendrecv", |
| "payloads": [], |
| "rtcp": {} |
| }); |
| var mblock = fillTemplate(templates.mblock, mediaDescription); |
| |
| var midBundleInfo = {"midLine": "", "bundleOnlyLine": ""}; |
| if (mediaDescription.mid) { |
| midBundleInfo.midLine = fillTemplate(templates.mid, mediaDescription); |
| if ((sdpObj.bundlePolicy == "balanced" && mediatypesBundle.includes(mediaDescription.type)) || |
| (sdpObj.bundlePolicy == "max-bundle" && mediatypesBundle.length > 0)) |
| midBundleInfo.bundleOnlyLine = "a=bundle-only\r\n"; |
| mediatypesBundle.push(mediaDescription.type) |
| midsBundle.push(mediaDescription.mid); |
| } |
| mblock = fillTemplate(mblock, midBundleInfo); |
| |
| var payloadInfo = {"rtpMapLines": "", "fmtpLines": "", "nackLines": "", |
| "nackpliLines": "", "ccmfirLines": "", "ericScreamLines": ""}; |
| mediaDescription.payloads.forEach(function (payload) { |
| if (payloadInfo.fmt) |
| payloadInfo.fmt += " " + payload.type; |
| else |
| payloadInfo.fmt = payload.type; |
| if (!payload.channels || payload.channels == 1) |
| payload.channels = null; |
| payloadInfo.rtpMapLines += fillTemplate(templates.rtpMap, payload); |
| if (payload.parameters) { |
| var fmtpInfo = { "type": payload.type, "parameters": "" }; |
| for (var p in payload.parameters) { |
| var param = p.replace(/([A-Z])([a-z])/g, function (_, a, b) { |
| return "-" + a.toLowerCase() + b; |
| }); |
| if (fmtpInfo.parameters) |
| fmtpInfo.parameters += ";"; |
| fmtpInfo.parameters += param + "=" + payload.parameters[p]; |
| } |
| payloadInfo.fmtpLines += fillTemplate(templates.fmtp, fmtpInfo); |
| } |
| if (payload.nack) |
| payloadInfo.nackLines += fillTemplate(templates.nack, payload); |
| if (payload.nackpli) |
| payloadInfo.nackpliLines += fillTemplate(templates.nackpli, payload); |
| if (payload.ccmfir) |
| payloadInfo.ccmfirLines += fillTemplate(templates.ccmfir, payload); |
| if (payload.ericscream) |
| payloadInfo.ericScreamLines += fillTemplate(templates.ericscream, payload); |
| }); |
| mblock = fillTemplate(mblock, payloadInfo); |
| |
| var rtcpInfo = {"rtcpLine": "", "rtcpMuxLine": ""}; |
| if (mediaDescription.rtcp.port) { |
| addDefaults(mediaDescription.rtcp, { |
| "netType": "IN", |
| "addressType": "IP4", |
| "address": "" |
| }); |
| if (!mediaDescription.rtcp.address) |
| mediaDescription.rtcp.netType = mediaDescription.rtcp.addressType = ""; |
| rtcpInfo.rtcpLine = fillTemplate(templates.rtcp, mediaDescription.rtcp); |
| } |
| if (mediaDescription.rtcp.mux) |
| rtcpInfo.rtcpMuxLine = templates.rtcpMux; |
| mblock = fillTemplate(mblock, rtcpInfo); |
| |
| var srcAttributeLines = { "cnameLines": "", "msidLines": "" }; |
| var srcAttributes = { |
| "cname": mediaDescription.cname, |
| "mediaStreamId": mediaDescription.mediaStreamId, |
| "mediaStreamTrackId": mediaDescription.mediaStreamTrackId |
| }; |
| if (mediaDescription.cname && mediaDescription.ssrcs) { |
| mediaDescription.ssrcs.forEach(function (ssrc) { |
| srcAttributes.ssrc = ssrc; |
| srcAttributeLines.cnameLines += fillTemplate(templates.cname, srcAttributes); |
| if (mediaDescription.mediaStreamId && mediaDescription.mediaStreamTrackId) |
| srcAttributeLines.msidLines += fillTemplate(templates.msid, srcAttributes); |
| }); |
| } else if (mediaDescription.mediaStreamId && mediaDescription.mediaStreamTrackId) { |
| srcAttributes.ssrc = null; |
| srcAttributeLines.msidLines += fillTemplate(templates.msid, srcAttributes); |
| } |
| mblock = fillTemplate(mblock, srcAttributeLines); |
| |
| var iceInfo = {"iceCredentialLines": "", "candidateLines": ""}; |
| if (mediaDescription.ice) { |
| iceInfo.iceCredentialLines = fillTemplate(templates.iceCredentials, |
| mediaDescription.ice); |
| if (mediaDescription.ice.candidates) { |
| mediaDescription.ice.candidates.forEach(function (candidate) { |
| addDefaults(candidate, { |
| "relatedAddress": null, |
| "relatedPort": null, |
| "tcpType": null |
| }); |
| iceInfo.candidateLines += fillTemplate(templates.candidate, candidate); |
| }); |
| } |
| } |
| mblock = fillTemplate(mblock, iceInfo); |
| |
| var dtlsInfo = { "dtlsFingerprintLine": "", "dtlsSetupLine": "" }; |
| if (mediaDescription.dtls) { |
| if (mediaDescription.dtls.fingerprint) { |
| dtlsInfo.dtlsFingerprintLine = fillTemplate(templates.dtlsFingerprint, |
| mediaDescription.dtls); |
| } |
| addDefaults(mediaDescription.dtls, {"setup": "actpass"}); |
| dtlsInfo.dtlsSetupLine = fillTemplate(templates.dtlsSetup, mediaDescription.dtls); |
| } |
| mblock = fillTemplate(mblock, dtlsInfo); |
| |
| var sctpInfo = {"sctpmapLine": "", "fmt": ""}; |
| if (mediaDescription.sctp) { |
| addDefaults(mediaDescription.sctp, {"streams": null}); |
| sctpInfo.sctpmapLine = fillTemplate(templates.sctpmap, mediaDescription.sctp); |
| sctpInfo.fmt = mediaDescription.sctp.port; |
| } |
| mblock = fillTemplate(mblock, sctpInfo); |
| |
| sdpText += mblock; |
| }); |
| |
| var bundleLine = ""; |
| if (midsBundle.length > 0) |
| bundleLine = fillTemplate(templates.bundle, { "midsBundle": midsBundle.join(" ") }); |
| sdpText = fillTemplate(sdpText, { "bundleLine": bundleLine }); |
| |
| return sdpText; |
| }; |
| |
| SDP.generateCandidateLine = function (candidateObj) { |
| addDefaults(candidateObj, { |
| "relatedAddress": null, |
| "relatedPort": null, |
| "tcpType": null |
| }); |
| |
| return fillTemplate(templates.candidate, candidateObj); |
| }; |
| |
| var expectedProperties = { |
| "session": [ "version", "originator", "sessionName", "startTime", "stopTime" ], |
| "mline": [ "type", "port", "protocol", "mode", "payloads", "rtcp", "dtls", "ice" ], |
| "mlineSubObjects": { |
| "rtcp": [ "mux" ], |
| "ice": [ "ufrag", "password" ], |
| "dtls": [ "setup", "fingerprintHashFunction", "fingerprint" ] |
| } |
| }; |
| |
| function hasAllProperties(object, properties) { |
| var missing = properties.filter(function (property) { |
| return !object.hasOwnProperty(property); |
| }); |
| |
| return !missing.length; |
| } |
| |
| SDP.verifyObject = function (sdpObj) { |
| if (!hasAllProperties(sdpObj, expectedProperties.session)) |
| return false; |
| |
| for (var i = 0; i < sdpObj.mediaDescriptions.length; i++) { |
| var mediaDescription = sdpObj.mediaDescriptions[i]; |
| |
| if (!hasAllProperties(mediaDescription, expectedProperties.mline)) |
| return false; |
| |
| for (var p in expectedProperties.mlineSubObjects) { |
| if (!hasAllProperties(mediaDescription[p], expectedProperties.mlineSubObjects[p])) |
| return false; |
| } |
| } |
| |
| return true; |
| }; |
| |
| })(); |
| |
| function generate(json) { |
| var object = JSON.parse(json); |
| return SDP.generate(object); |
| } |
| |
| function parse(sdp) { |
| var object = SDP.parse(sdp); |
| |
| if (!SDP.verifyObject(object)) |
| return "ParseError"; |
| |
| return JSON.stringify(object); |
| } |
| |
| function generateCandidateLine(json) { |
| var candidate = JSON.parse(json); |
| return SDP.generateCandidateLine(candidate).substr(2); |
| } |
| |
| function parseCandidateLine(candidateLine) { |
| var mdesc = SDP.parse("m=application 0 NONE\r\na=" + candidateLine + "\r\n").mediaDescriptions[0]; |
| if (!mdesc.ice) |
| return "ParseError"; |
| |
| return JSON.stringify(mdesc.ice.candidates[0]); |
| } |
| |
| if (typeof(module) != "undefined" && typeof(exports) != "undefined") |
| module.exports = SDP; |