@jitsi/sdp-interop
Advanced tools
Comparing version 0.1.14 to 1.0.0
@@ -1,2 +0,2 @@ | ||
/* Copyright @ 2015 Atlassian Pty Ltd | ||
/* Copyright @ 2015 - Present, 8x8 Inc | ||
* | ||
@@ -16,2 +16,2 @@ * Licensed under the Apache License, Version 2.0 (the "License"); | ||
exports.Interop = require('./interop'); | ||
export * from './interop'; |
1044
lib/interop.js
@@ -1,2 +0,2 @@ | ||
/* Copyright @ 2015 Atlassian Pty Ltd | ||
/* Copyright @ 2015 - Present, 8x8 Inc | ||
* | ||
@@ -16,825 +16,419 @@ * Licensed under the Apache License, Version 2.0 (the "License"); | ||
/* global RTCSessionDescription */ | ||
/* global RTCIceCandidate */ | ||
/* jshint -W097 */ | ||
"use strict"; | ||
import clonedeep from 'lodash.clonedeep'; | ||
import transform from './transform.js'; | ||
var transform = require('./transform'); | ||
var arrayEquals = require('./array-equals'); | ||
const PLAN_B_MIDS = [ 'audio', 'video', 'data' ]; | ||
const findSimGroup = ssrcGroup => ssrcGroup.find(grp => grp.semantics === 'SIM'); | ||
const findFidGroup = ssrcGroup => ssrcGroup.find(grp => grp.semantics === 'FID'); | ||
/** | ||
* Unified Plan mids may be parsed as integers | ||
* Add the ssrcs of the SIM group and their corresponding FID group ssrcs | ||
* to the m-line. | ||
* @param {Object} mLine - The m-line to which ssrcs have to be added. | ||
* @param {Object} simGroup - The SIM group whose ssrcs have to be added to | ||
* the m-line. | ||
* @param {Object} sourceGroups - inverted source-group map. | ||
* @param {Array<Object>} sourceList - array containing all the sources. | ||
*/ | ||
function midToString(line) { | ||
if (typeof line.mid === 'number') { | ||
line.mid = line.mid.toString(); | ||
function addSimGroupSources(mLine, simGroup, sourceGroups, sourceList) { | ||
if (!mLine || !simGroup) { | ||
return; | ||
} | ||
} | ||
const findSourcebyId = src => sourceList.find(source => source.id.toString() === src); | ||
simGroup.ssrcs.forEach(src => { | ||
mLine.sources.push(findSourcebyId(src)); | ||
function Interop() { | ||
// find the related FID group member for this ssrc. | ||
const relatedFidGroup = sourceGroups[parseInt(src, 10)].find(grp => grp.semantics === 'FID'); | ||
/** | ||
* This map holds the most recent Unified Plan offer/answer SDP that was | ||
* converted to Plan B, with the SDP type ('offer' or 'answer') as keys and | ||
* the SDP string as values. | ||
* | ||
* @type {{}} | ||
*/ | ||
this.cache = { | ||
mlB2UMap : {}, | ||
mlU2BMap : {} | ||
}; | ||
} | ||
if (relatedFidGroup) { | ||
const relatedSsrc = relatedFidGroup.ssrcs.find(s => s !== src); | ||
module.exports = Interop; | ||
/** | ||
* Changes the candidate args to match with the related Unified Plan | ||
*/ | ||
Interop.prototype.candidateToUnifiedPlan = function(candidate) { | ||
var cand = new RTCIceCandidate(candidate); | ||
cand.sdpMLineIndex = this.cache.mlB2UMap[cand.sdpMLineIndex]; | ||
/* TODO: change sdpMid to (audio|video)-SSRC */ | ||
return cand; | ||
}; | ||
/** | ||
* Changes the candidate args to match with the related Plan B | ||
*/ | ||
Interop.prototype.candidateToPlanB = function(candidate) { | ||
var cand = new RTCIceCandidate(candidate); | ||
if (cand.sdpMid.indexOf('audio') === 0) { | ||
cand.sdpMid = 'audio'; | ||
} else if (cand.sdpMid.indexOf('video') === 0) { | ||
cand.sdpMid = 'video'; | ||
} else { | ||
throw new Error('candidate with ' + cand.sdpMid + ' not allowed'); | ||
} | ||
cand.sdpMLineIndex = this.cache.mlU2BMap[cand.sdpMLineIndex]; | ||
return cand; | ||
}; | ||
/** | ||
* Returns the index of the first m-line with the given media type and with a | ||
* direction which allows sending, in the last Unified Plan description with | ||
* type "answer" converted to Plan B. Returns {null} if there is no saved | ||
* answer, or if none of its m-lines with the given type allow sending. | ||
* @param type the media type ("audio" or "video"). | ||
* @returns {*} | ||
*/ | ||
Interop.prototype.getFirstSendingIndexFromAnswer = function(type) { | ||
if (!this.cache.answer) { | ||
return null; | ||
} | ||
var session = transform.parse(this.cache.answer); | ||
if (session && session.media && Array.isArray(session.media)){ | ||
for (var i = 0; i < session.media.length; i++) { | ||
if (session.media[i].type == type && | ||
(!session.media[i].direction /* default to sendrecv */ || | ||
session.media[i].direction === 'sendrecv' || | ||
session.media[i].direction === 'sendonly')){ | ||
return i; | ||
} | ||
mLine.sources.push(findSourcebyId(relatedSsrc)); | ||
mLine.ssrcGroups.push(relatedFidGroup); | ||
} | ||
} | ||
}); | ||
return null; | ||
}; | ||
// Add the SIM group last. | ||
mLine.ssrcGroups.push(simGroup); | ||
} | ||
/** | ||
* This method transforms a Unified Plan SDP to an equivalent Plan B SDP. A | ||
* PeerConnection wrapper transforms the SDP to Plan B before passing it to the | ||
* application. | ||
* | ||
* @param desc | ||
* @returns {*} | ||
* Add ssrcs and ssrc-groups to the m-line. When a primary ssrc, i.e., the | ||
* first ssrc in a SIM group is passed, all the other ssrcs from the SIM | ||
* group and the other ssrcs from the related FID groups are added to the same | ||
* m-line since they all belong to the same remote source. Since the ssrcs are | ||
* not guaranteed to be in the correct order, try to find if a SIM group exists, | ||
* if not, just add the FID group. | ||
* @param {Object} mLine - The m-line to which ssrcs have to be added. | ||
* @param {Object} ssrc - the primary ssrc. | ||
* @param {Object} sourceGroups - inverted source-group map. | ||
* @param {Array<Object>} sourceList - array containing all the sources. | ||
* @returns {void} | ||
*/ | ||
Interop.prototype.toPlanB = function(desc) { | ||
var self = this; | ||
//#region Preliminary input validation. | ||
if (typeof desc !== 'object' || desc === null || | ||
typeof desc.sdp !== 'string') { | ||
console.warn('An empty description was passed as an argument.'); | ||
return desc; | ||
function addSourcesToMline(mLine, ssrc, sourceGroups, sourceList) { | ||
if (!mLine || !ssrc) { | ||
return; | ||
} | ||
mLine.sources = []; | ||
mLine.ssrcGroups = []; | ||
// Objectify the SDP for easier manipulation. | ||
var session = transform.parse(desc.sdp); | ||
// If there are no associated ssrc-groups, just add the ssrc and msid. | ||
if (!sourceGroups[ssrc.id]) { | ||
mLine.sources.push(ssrc); | ||
mLine.msid = ssrc.msid; | ||
// If the SDP contains no media, there's nothing to transform. | ||
if (typeof session.media === 'undefined' || | ||
!Array.isArray(session.media) || session.media.length === 0) { | ||
console.warn('The description has no media.'); | ||
return desc; | ||
return; | ||
} | ||
const findSourcebyId = src => sourceList.find(source => source.id.toString() === src); | ||
// Try some heuristics to "make sure" this is a Unified Plan SDP. Plan B | ||
// SDP has a video, an audio and a data "channel" at most. | ||
if (session.media.length <= 3 && session.media.every(function(m) { | ||
return ['video', 'audio', 'data'].indexOf(m.mid) !== -1; | ||
})) { | ||
console.warn('This description does not look like Unified Plan.'); | ||
return desc; | ||
} | ||
// Find the SIM and FID groups that this ssrc belongs to. | ||
const simGroup = findSimGroup(sourceGroups[ssrc.id]); | ||
const fidGroup = findFidGroup(sourceGroups[ssrc.id]); | ||
//#endregion | ||
// Add the ssrcs for the SIM group and their corresponding FID groups. | ||
if (simGroup) { | ||
addSimGroupSources(mLine, simGroup, sourceGroups, sourceList); | ||
} else if (fidGroup) { | ||
// check if the other ssrc from this FID group is part of a SIM group | ||
const otherSsrc = fidGroup.ssrcs.find(s => s !== ssrc); | ||
const simGroup2 = findSimGroup(sourceGroups[otherSsrc]); | ||
// HACK https://bugzilla.mozilla.org/show_bug.cgi?id=1113443 | ||
var sdp = desc.sdp; | ||
var rewrite = false; | ||
for (var i = 0; i < session.media.length; i++) { | ||
var uLine = session.media[i]; | ||
uLine.rtp.forEach(function(rtp) { | ||
if (rtp.codec === 'NULL') | ||
{ | ||
rewrite = true; | ||
var offer = transform.parse(self.cache.offer); | ||
rtp.codec = offer.media[i].rtp[0].codec; | ||
} | ||
}); | ||
if (simGroup2) { | ||
addSimGroupSources(mLine, simGroup2, sourceGroups, sourceList); | ||
} else { | ||
// Add the FID group ssrcs. | ||
fidGroup.ssrcs.forEach(src => { | ||
mLine.sources.push(findSourcebyId(src)); | ||
}); | ||
mLine.ssrcGroups.push(fidGroup); | ||
} | ||
} | ||
if (rewrite) { | ||
sdp = transform.write(session); | ||
} | ||
// Unified Plan SDP is our "precious". Cache it for later use in the Plan B | ||
// -> Unified Plan transformation. | ||
this.cache[desc.type] = sdp; | ||
// Set the msid for the media description using the msid attribute of the ssrcs. | ||
mLine.msid = mLine.sources[0].msid; | ||
} | ||
//#region Convert from Unified Plan to Plan B. | ||
/** | ||
* Checks if there is a mline for the given ssrc or its related primary ssrc. | ||
* We always implode the SIM group to the first ssrc in the SIM group before sRD, | ||
* so we also check if mline for that ssrc exists. | ||
* For example: | ||
* If the following ssrcs are in a SIM group, | ||
* <ssrc-group xmlns=\"urn:xmpp:jingle:apps:rtp:ssma:0\" semantics=\"SIM\"> | ||
* <source ssrc=\"1806330949\"/> | ||
* <source ssrc=\"4173145196\"/> | ||
* <source ssrc=\"2002632207\"/> | ||
* </ssrc-group> | ||
* This method returns true for any one of the 3 ssrcs if there is a mline for 1806330949. | ||
* @param {Object} ssrc - ssrc to check. | ||
* @param {Object} sourceGroups - inverted source-group map. | ||
* @param {Array<Object>} mlines - mlines in the description | ||
// We rebuild the session.media array. | ||
var media = session.media; | ||
session.media = []; | ||
* @returns {Boolean} - Returns true if mline for the given ssrc or the related primary ssrc | ||
* exists, returns false otherwise. | ||
*/ | ||
function checkIfMlineForSsrcExists(ssrc, sourceGroups, mlines) { | ||
const findMatchingMline = mline => { | ||
if (mline.sources) { | ||
return mline.sources.some(source => source.id === ssrc.id); | ||
} | ||
// Associative array that maps channel types to channel objects for fast | ||
// access to channel objects by their type, e.g. type2bl['audio']->channel | ||
// obj. | ||
var type2bl = {}; | ||
return false; | ||
}; | ||
// Used to build the group:BUNDLE value after the channels construction | ||
// loop. | ||
var types = []; | ||
if (!mlines.find(findMatchingMline)) { | ||
// check if this ssrc is member of a SIM group. If so, check if there | ||
// is a matching m-line for the primary ssrc of the SIM group. | ||
if (!sourceGroups[ssrc.id]) { | ||
return false; | ||
} | ||
const simGroup = findSimGroup(sourceGroups[ssrc.id]); | ||
const fidGroup = findFidGroup(sourceGroups[ssrc.id]); | ||
// Used to aggregate the directions of the m-lines. | ||
var directionResult = {}; | ||
if (simGroup) { | ||
return mlines.some(mline => mline.sources | ||
&& mline.sources.some(src => src.id.toString() === simGroup.ssrcs[0])); | ||
} else if (fidGroup && ssrc.id.toString() !== fidGroup.ssrcs[0]) { | ||
const otherSsrc = { id: fidGroup.ssrcs[0] }; | ||
media.forEach(function(uLine) { | ||
midToString(uLine); | ||
// rtcp-mux is required in the Plan B SDP. | ||
if ((typeof uLine.rtcpMux !== 'string' || | ||
uLine.rtcpMux !== 'rtcp-mux') && | ||
uLine.direction !== 'inactive' && uLine.type !== 'application') { | ||
throw new Error('Cannot convert to Plan B because m-lines ' + | ||
'without the rtcp-mux attribute were found.'); | ||
} | ||
return checkIfMlineForSsrcExists(otherSsrc, sourceGroups, mlines); | ||
// If we don't have a channel for this uLine.type OR the selected is | ||
// inactive, then select this uLine as the channel basis. | ||
if (typeof type2bl[uLine.type] === 'undefined' || | ||
type2bl[uLine.type].direction === 'inactive') { | ||
type2bl[uLine.type] = uLine; | ||
} | ||
}); | ||
// Implode the Unified Plan m-lines/tracks into Plan B channels. | ||
media.forEach(function(uLine) { | ||
var type = uLine.type; | ||
return false; | ||
} | ||
if (type === 'application') { | ||
uLine.mid = "data"; | ||
session.media.push(uLine); | ||
types.push(uLine.mid); | ||
return; | ||
} | ||
return true; | ||
} | ||
// Add sources to the channel and handle a=msid. | ||
if (typeof uLine.sources === 'object') { | ||
Object.keys(uLine.sources).forEach(function(ssrc) { | ||
if (typeof type2bl[type].sources !== 'object') | ||
type2bl[type].sources = {}; | ||
/** | ||
* Create an inverted sourceGroup map to put all the grouped ssrcs | ||
* in the same m-line. | ||
* @param {Array<Object>} sourceGroups | ||
* @returns {Object} - An inverted sourceGroup map. | ||
*/ | ||
function createSourceGroupMap(sourceGroups) { | ||
const ssrc2group = {}; | ||
// Assign the sources to the channel. | ||
type2bl[type].sources[ssrc] = uLine.sources[ssrc]; | ||
if (typeof uLine.msid !== 'undefined') { | ||
// In Plan B the msid is an SSRC attribute. Also, we don't | ||
// care about the obsolete label and mslabel attributes. | ||
// | ||
// Note that it is not guaranteed that the uLine will | ||
// have an msid. recvonly channels in particular don't have | ||
// one. | ||
type2bl[type].sources[ssrc].msid = uLine.msid; | ||
if (!sourceGroups || !Array.isArray(sourceGroups)) { | ||
return ssrc2group; | ||
} | ||
sourceGroups.forEach(group => { | ||
if (group.ssrcs && Array.isArray(group.ssrcs)) { | ||
group.ssrcs.forEach(ssrc => { | ||
if (typeof ssrc2group[ssrc] === 'undefined') { | ||
ssrc2group[ssrc] = []; | ||
} | ||
// NOTE ssrcs in ssrc groups will share msids, as | ||
// draft-uberti-rtcweb-plan-00 mandates. | ||
ssrc2group[ssrc].push(group); | ||
}); | ||
} | ||
}); | ||
// Add ssrc groups to the channel. | ||
if (typeof uLine.ssrcGroups !== 'undefined' && | ||
Array.isArray(uLine.ssrcGroups)) { | ||
return ssrc2group; | ||
} | ||
// Create the ssrcGroups array, if it's not defined. | ||
if (typeof type2bl[type].ssrcGroups === 'undefined' || | ||
!Array.isArray(type2bl[type].ssrcGroups)) { | ||
type2bl[type].ssrcGroups = []; | ||
} | ||
/** | ||
* Interop provides an API for tranforming a Plan B SDP to a Unified Plan SDP and | ||
* vice versa. | ||
*/ | ||
export class Interop { | ||
/** | ||
* This method transforms a Unified Plan SDP to an equivalent Plan B SDP. | ||
* @param {RTCSessionDescription} description - The description in Unified plan format. | ||
* @returns RTCSessionDescription - The transformed session description. | ||
*/ | ||
toPlanB(description) { | ||
if (!description || typeof description.sdp !== 'string') { | ||
console.warn('An empty description was passed as an argument.'); | ||
// Different ssrc may belong to the same group | ||
if (!arrayEquals.apply(type2bl[type].ssrcGroups, | ||
[uLine.ssrcGroups])) { | ||
type2bl[type].ssrcGroups | ||
= type2bl[type].ssrcGroups.concat(uLine.ssrcGroups); | ||
} | ||
return description; | ||
} | ||
var direction = uLine.direction; | ||
// Objectify the SDP for easier manipulation. | ||
const session = transform.parse(description.sdp); | ||
directionResult[type] | ||
= (directionResult[type] || 0 /* inactive */) | ||
| directionMasks[direction || 'inactive']; | ||
// If the SDP contains no media, there's nothing to transform. | ||
if (!session.media || !session.media.length) { | ||
console.warn('The description has no media.'); | ||
if (type2bl[type] === uLine) { | ||
// Plan B mids are in ['audio', 'video', 'data'] | ||
uLine.mid = type; | ||
return description; | ||
} | ||
// Plan B doesn't support/need the bundle-only attribute. | ||
delete uLine.bundleOnly; | ||
// Make sure this is a unified plan sdp | ||
if (session.media.every(m => PLAN_B_MIDS.indexOf(m.mid) !== -1)) { | ||
console.warn('The description does not look like unified plan sdp'); | ||
// In Plan B the msid is an SSRC attribute. | ||
delete uLine.msid; | ||
if (direction !== 'inactive') { | ||
// Used to build the group:BUNDLE value after this loop. | ||
types.push(type); | ||
} | ||
// Add the channel to the new media array. | ||
session.media.push(uLine); | ||
return description; | ||
} | ||
}); | ||
// We regenerate the BUNDLE group with the new mids. | ||
session.groups.some(function(group) { | ||
if (group.type === 'BUNDLE') { | ||
group.mids = types.join(' '); | ||
return true; | ||
} | ||
}); | ||
const media = {}; | ||
const sessionMedia = session.media; | ||
// msid semantic | ||
session.msidSemantic = { | ||
semantic: 'WMS', | ||
token: '*' | ||
}; | ||
session.media = []; | ||
sessionMedia.forEach(mLine => { | ||
const type = mLine.type; | ||
var resStr = transform.write(session); | ||
if (type === 'application') { | ||
mLine.mid = 'data'; | ||
media[mLine.mid] = mLine; | ||
return new RTCSessionDescription({ | ||
type: desc.type, | ||
sdp: resStr | ||
}); | ||
return; | ||
} | ||
if (typeof media[type] === 'undefined') { | ||
const bLine = clonedeep(mLine); | ||
//#endregion | ||
}; | ||
// Copy the msid attribute to all the ssrcs if they belong to the same source group | ||
if (bLine.sources && Array.isArray(bLine.sources)) { | ||
bLine.sources.forEach(source => { | ||
mLine.msid ? source.msid = mLine.msid : delete source.msid; | ||
}); | ||
} | ||
if (!bLine.ssrcGroups) { | ||
bLine.ssrcGroups = []; | ||
} | ||
delete bLine.msid; | ||
bLine.mid = type; | ||
media[type] = bLine; | ||
} else if (mLine.msid) { | ||
// Add sources and source-groups to the existing m-line of the same media type. | ||
if (mLine.sources && Array.isArray(mLine.sources)) { | ||
media[type].sources = media[type].sources.concat(mLine.sources); | ||
} | ||
if (typeof mLine.ssrcGroups !== 'undefined' && Array.isArray(mLine.ssrcGroups)) { | ||
media[type].ssrcGroups = media[type].ssrcGroups.concat(mLine.ssrcGroups); | ||
} | ||
} | ||
}); | ||
session.media = Object.values(media); | ||
/** | ||
* This method transforms a Plan B SDP to an equivalent Unified Plan SDP. A | ||
* PeerConnection wrapper transforms the SDP to Unified Plan before passing it | ||
* to FF. | ||
* | ||
* @param desc | ||
* @returns {*} | ||
*/ | ||
Interop.prototype.toUnifiedPlan = function(desc) { | ||
var self = this; | ||
//#region Preliminary input validation. | ||
// Bundle the media only if it is active. | ||
const bundle = []; | ||
if (typeof desc !== 'object' || desc === null || | ||
typeof desc.sdp !== 'string') { | ||
console.warn('An empty description was passed as an argument.'); | ||
return desc; | ||
} | ||
Object.values(media).forEach(mline => { | ||
if (mline.direction !== 'inactive') { | ||
bundle.push(mline.mid); | ||
} | ||
}); | ||
var session = transform.parse(desc.sdp); | ||
// We regenerate the BUNDLE group with the new mids. | ||
session.groups.forEach(group => { | ||
if (group.type === 'BUNDLE') { | ||
group.mids = bundle.join(' '); | ||
} | ||
}); | ||
// If the SDP contains no media, there's nothing to transform. | ||
if (typeof session.media === 'undefined' || | ||
!Array.isArray(session.media) || session.media.length === 0) { | ||
console.warn('The description has no media.'); | ||
return desc; | ||
} | ||
// msid semantic | ||
session.msidSemantic = { | ||
semantic: 'WMS', | ||
token: '*' | ||
}; | ||
const resStr = transform.write(session); | ||
// Try some heuristics to "make sure" this is a Plan B SDP. Plan B SDP has | ||
// a video, an audio and a data "channel" at most. | ||
if (session.media.length > 3 || !session.media.every(function(m) { | ||
return ['video', 'audio', 'data'].indexOf(m.mid) !== -1; | ||
})) { | ||
console.warn('This description does not look like Plan B.'); | ||
return desc; | ||
} | ||
// Make sure this Plan B SDP can be converted to a Unified Plan SDP. | ||
var mids = []; | ||
session.media.forEach(function(m) { | ||
mids.push(m.mid); | ||
}); | ||
var hasBundle = false; | ||
if (typeof session.groups !== 'undefined' && | ||
Array.isArray(session.groups)) { | ||
hasBundle = session.groups.every(function(g) { | ||
return g.type !== 'BUNDLE' || | ||
arrayEquals.apply(g.mids.sort(), [mids.sort()]); | ||
return new RTCSessionDescription({ | ||
type: description.type, | ||
sdp: resStr | ||
}); | ||
} | ||
if (!hasBundle) { | ||
throw new Error("Cannot convert to Unified Plan because m-lines that" + | ||
" are not bundled were found."); | ||
} | ||
/** | ||
* This method transforms a Plan B SDP to an equivalent Unified Plan SDP. | ||
* @param {RTCSessionDescription} description - The description in plan-b format. | ||
* @param {RTCSessionDescription} current - The current description set on | ||
* the peerconnection in Unified-plan format, i.e., the readonly attribute | ||
* remoteDescription on the RTCPeerConnection object. | ||
* @returns RTCSessionDescription - The transformed session description. | ||
*/ | ||
toUnifiedPlan(description, current = null) { | ||
if (!description || typeof description.sdp !== 'string') { | ||
console.warn('An empty description was passed as an argument.'); | ||
//#endregion | ||
return description; | ||
} | ||
// Objectify the SDP for easier manipulation. | ||
const session = transform.parse(description.sdp); | ||
//#region Convert from Plan B to Unified Plan. | ||
// If the SDP contains no media, there's nothing to transform. | ||
if (!session.media || !session.media.length) { | ||
console.warn('The description has no media.'); | ||
// Unfortunately, a Plan B offer/answer doesn't have enough information to | ||
// rebuild an equivalent Unified Plan offer/answer. | ||
// | ||
// For example, if this is a local answer (in Unified Plan style) that we | ||
// convert to Plan B prior to handing it over to the application (the | ||
// PeerConnection wrapper called us, for instance, after a successful | ||
// createAnswer), we want to remember the m-line at which we've seen the | ||
// (local) SSRC. That's because when the application wants to do call the | ||
// SLD method, forcing us to do the inverse transformation (from Plan B to | ||
// Unified Plan), we need to know to which m-line to assign the (local) | ||
// SSRC. We also need to know all the other m-lines that the original | ||
// answer had and include them in the transformed answer as well. | ||
// | ||
// Another example is if this is a remote offer that we convert to Plan B | ||
// prior to giving it to the application, we want to remember the mid at | ||
// which we've seen the (remote) SSRC. | ||
// | ||
// In the iteration that follows, we use the cached Unified Plan (if it | ||
// exists) to assign mids to ssrcs. | ||
return description; | ||
} | ||
var cached; | ||
if (typeof this.cache[desc.type] !== 'undefined') { | ||
cached = transform.parse(this.cache[desc.type]); | ||
} | ||
// Make sure this is a plan-b sdp. | ||
if (session.media.length > 3 || session.media.every(m => PLAN_B_MIDS.indexOf(m.mid) === -1)) { | ||
console.warn('The description does not look like plan-b'); | ||
var recvonlySsrcs = { | ||
audio: {}, | ||
video: {} | ||
}; | ||
// A helper map that sends mids to m-line objects. We use it later to | ||
// rebuild the Unified Plan style session.media array. | ||
var mid2ul = {}; | ||
var bIdx = 0; | ||
var uIdx = 0; | ||
session.media.forEach(function(bLine) { | ||
if ((typeof bLine.rtcpMux !== 'string' || | ||
bLine.rtcpMux !== 'rtcp-mux') && | ||
bLine.direction !== 'inactive' && bLine.type !== 'application') { | ||
throw new Error("Cannot convert to Unified Plan because m-lines " + | ||
"without the rtcp-mux attribute were found."); | ||
return description; | ||
} | ||
const currentDesc = current ? transform.parse(current.sdp) : null; | ||
const media = {}; | ||
if (bLine.type === 'application') { | ||
var uLineData = null; | ||
if (cached && cached.media) { | ||
uLineData = cached.media.find(function(uLine) { | ||
return uLine.type === 'application'; | ||
}); | ||
} | ||
if (uLineData) { | ||
mid2ul[uLineData.mid] = uLineData; | ||
} else { | ||
mid2ul[bLine.mid] = bLine; | ||
} | ||
return; | ||
} | ||
session.media.forEach(mLine => { | ||
const type = mLine.type; | ||
// With rtcp-mux and bundle all the channels should have the same ICE | ||
// stuff. | ||
var sources = bLine.sources; | ||
var ssrcGroups = bLine.ssrcGroups; | ||
var candidates = bLine.candidates; | ||
var iceUfrag = bLine.iceUfrag; | ||
var icePwd = bLine.icePwd; | ||
var fingerprint = bLine.fingerprint; | ||
var port = bLine.port; | ||
if (type === 'application') { | ||
if (!currentDesc || !currentDesc.media) { | ||
const newMline = clonedeep(mLine); | ||
// We'll use the "bLine" object as a prototype for each new "mLine" | ||
// that we create, but first we need to clean it up a bit. | ||
delete bLine.sources; | ||
delete bLine.ssrcGroups; | ||
delete bLine.candidates; | ||
delete bLine.iceUfrag; | ||
delete bLine.icePwd; | ||
delete bLine.fingerprint; | ||
delete bLine.port; | ||
delete bLine.mid; | ||
newMline.mid = Object.keys(media).length.toString(); | ||
media[mLine.mid] = newMline; | ||
// inverted ssrc group map | ||
var ssrc2group = {}; | ||
if (typeof ssrcGroups !== 'undefined' && Array.isArray(ssrcGroups)) { | ||
ssrcGroups.forEach(function (ssrcGroup) { | ||
// TODO(gp) find out how to receive simulcast with FF. For the | ||
// time being, hide it. | ||
if (ssrcGroup.semantics === 'SIM') { | ||
return; | ||
} | ||
const mLineForData = currentDesc.media.findIndex(m => m.type === type); | ||
// XXX This might brake if an SSRC is in more than one group | ||
// for some reason. | ||
if (typeof ssrcGroup.ssrcs !== 'undefined' && | ||
Array.isArray(ssrcGroup.ssrcs)) { | ||
ssrcGroup.ssrcs.forEach(function (ssrc) { | ||
if (typeof ssrc2group[ssrc] === 'undefined') { | ||
ssrc2group[ssrc] = []; | ||
} | ||
ssrc2group[ssrc].push(ssrcGroup); | ||
}); | ||
if (mLineForData) { | ||
currentDesc.media[mLineForData] = mLine; | ||
} | ||
}); | ||
} | ||
// ssrc to m-line index. | ||
var ssrc2ml = {}; | ||
return; | ||
} | ||
if (typeof sources === 'object') { | ||
// Create an inverted sourceGroup map here to put all the grouped SSRCs in the same m-line. | ||
const ssrc2group = createSourceGroupMap(mLine.ssrcGroups); | ||
// Explode the Plan B channel sources with one m-line per source. | ||
Object.keys(sources).forEach(function(ssrc) { | ||
if (!mLine.sources) { | ||
return; | ||
} | ||
mLine.sources.forEach((ssrc, idx) => { | ||
// Do not add the receive-only ssrcs that Jicofo sends in the source-add. | ||
// These ssrcs do not have the "msid" attribute set. | ||
if (!ssrc.msid) { | ||
return; | ||
} | ||
// The (unified) m-line for this SSRC. We either create it from | ||
// scratch or, if it's a grouped SSRC, we re-use a related | ||
// mline. In other words, if the source is grouped with another | ||
// source, put the two together in the same m-line. | ||
var uLine; | ||
// We assume here that we are the answerer in the O/A, so any | ||
// offers which we translate come from the remote side, while | ||
// answers are local. So the check below is to make that we | ||
// handle receive-only SSRCs in a special way only if they come | ||
// from the remote side. | ||
if (desc.type==='offer') { | ||
// We want to detect SSRCs which are used by a remote peer | ||
// in an m-line with direction=recvonly (i.e. they are | ||
// being used for RTCP only). | ||
// This information would have gotten lost if the remote | ||
// peer used Unified Plan and their local description was | ||
// translated to Plan B. So we use the lack of an MSID | ||
// attribute to deduce a "receive only" SSRC. | ||
if (!sources[ssrc].msid) { | ||
recvonlySsrcs[bLine.type][ssrc] = sources[ssrc]; | ||
// Receive-only SSRCs must not create new m-lines. We | ||
// will assign them to an existing m-line later. | ||
// If there is no description set on the peerconnection, create new m-lines. | ||
if (!currentDesc || !currentDesc.media) { | ||
if (checkIfMlineForSsrcExists(ssrc, ssrc2group, Object.values(media))) { | ||
return; | ||
} | ||
} | ||
const newMline = clonedeep(mLine); | ||
if (typeof ssrc2group[ssrc] !== 'undefined' && | ||
Array.isArray(ssrc2group[ssrc])) { | ||
ssrc2group[ssrc].some(function (ssrcGroup) { | ||
// ssrcGroup.ssrcs *is* an Array, no need to check | ||
// again here. | ||
return ssrcGroup.ssrcs.some(function (related) { | ||
if (typeof ssrc2ml[related] === 'object') { | ||
uLine = ssrc2ml[related]; | ||
return true; | ||
} | ||
}); | ||
}); | ||
newMline.mid = Object.keys(media).length.toString(); | ||
newMline.direction = idx | ||
? 'sendonly' | ||
: mLine.direction === 'sendonly' ? 'sendonly' : 'sendrecv'; | ||
newMline.bundleOnly = undefined; | ||
addSourcesToMline(newMline, ssrc, ssrc2group, mLine.sources); | ||
media[newMline.mid] = newMline; | ||
return; | ||
} | ||
if (typeof uLine === 'object') { | ||
// the m-line already exists. Just add the source. | ||
uLine.sources[ssrc] = sources[ssrc]; | ||
delete sources[ssrc].msid; | ||
} else { | ||
// Use the "bLine" as a prototype for the "uLine". | ||
uLine = Object.create(bLine); | ||
ssrc2ml[ssrc] = uLine; | ||
if (typeof sources[ssrc].msid !== 'undefined') { | ||
// Assign the msid of the source to the m-line. Note | ||
// that it is not guaranteed that the source will have | ||
// msid. In particular "recvonly" sources don't have an | ||
// msid. Note that "recvonly" is a term only defined | ||
// for m-lines. | ||
uLine.msid = sources[ssrc].msid; | ||
delete sources[ssrc].msid; | ||
} | ||
// We assign one SSRC per media line. | ||
uLine.sources = {}; | ||
uLine.sources[ssrc] = sources[ssrc]; | ||
uLine.ssrcGroups = ssrc2group[ssrc]; | ||
// Use the cached Unified Plan SDP (if it exists) to assign | ||
// SSRCs to mids. | ||
if (typeof cached !== 'undefined' && | ||
typeof cached.media !== 'undefined' && | ||
Array.isArray(cached.media)) { | ||
cached.media.forEach(function (m) { | ||
if (typeof m.sources === 'object') { | ||
Object.keys(m.sources).forEach(function (s) { | ||
if (s === ssrc) { | ||
uLine.mid = m.mid; | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
midToString(uLine); | ||
if (typeof uLine.mid === 'undefined') { | ||
// If this is an SSRC that we see for the first time | ||
// assign it a new mid. This is typically the case when | ||
// this method is called to transform a remote | ||
// description for the first time or when there is a | ||
// new SSRC in the remote description because a new | ||
// peer has joined the conference. Local SSRCs should | ||
// have already been added to the map in the toPlanB | ||
// method. | ||
// | ||
// Because FF generates answers in Unified Plan style, | ||
// we MUST already have a cached answer with all the | ||
// local SSRCs mapped to some m-line/mid. | ||
if (desc.type === 'answer') { | ||
throw new Error("An unmapped SSRC was found."); | ||
} | ||
uLine.mid = [bLine.type, '-', ssrc].join(''); | ||
} | ||
// Include the candidates in the 1st media line. | ||
uLine.candidates = candidates; | ||
uLine.iceUfrag = iceUfrag; | ||
uLine.icePwd = icePwd; | ||
uLine.fingerprint = fingerprint; | ||
uLine.port = port; | ||
mid2ul[uLine.mid] = uLine; | ||
self.cache.mlU2BMap[uIdx] = bIdx; | ||
if (typeof self.cache.mlB2UMap[bIdx] === 'undefined') { | ||
self.cache.mlB2UMap[bIdx] = uIdx; | ||
} | ||
uIdx++; | ||
// Create and append the m-lines to the existing description. | ||
if (checkIfMlineForSsrcExists(ssrc, ssrc2group, currentDesc.media)) { | ||
return; | ||
} | ||
}); | ||
} | ||
bIdx++; | ||
}); | ||
// check if there is a m-line that is inactive and is of the same media type | ||
const inactiveMid = currentDesc.media | ||
.findIndex(cmLine => cmLine.direction | ||
&& cmLine.direction === 'inactive' | ||
&& cmLine.type === type); | ||
// Rebuild the media array in the right order and add the missing mLines | ||
// (missing from the Plan B SDP). | ||
session.media = []; | ||
mids = []; // reuse | ||
if (inactiveMid > -1) { | ||
currentDesc.media[inactiveMid].direction = 'sendonly'; | ||
addSourcesToMline(currentDesc.media[inactiveMid], ssrc, ssrc2group, mLine.sources); | ||
} else { | ||
const newMline = clonedeep(mLine); | ||
if (desc.type === 'answer') { | ||
// The media lines in the answer must match the media lines in the | ||
// offer. The order is important too. Here we assume that Firefox is | ||
// the answerer, so we merely have to use the reconstructed (unified) | ||
// answer to update the cached (unified) answer accordingly. | ||
// | ||
// In the general case, one would have to use the cached (unified) | ||
// offer to find the m-lines that are missing from the reconstructed | ||
// answer, potentially grabbing them from the cached (unified) answer. | ||
// One has to be careful with this approach because inactive m-lines do | ||
// not always have an mid, making it tricky (impossible?) to find where | ||
// exactly and which m-lines are missing from the reconstructed answer. | ||
for (var i = 0; i < cached.media.length; i++) { | ||
var uLine = cached.media[i]; | ||
midToString(uLine); | ||
if (typeof mid2ul[uLine.mid] === 'undefined') { | ||
// The mid isn't in the reconstructed (unified) answer. | ||
// This is either a (unified) m-line containing a remote | ||
// track only, or a (unified) m-line containing a remote | ||
// track and a local track that has been removed. | ||
// In either case, it MUST exist in the cached | ||
// (unified) answer. | ||
// | ||
// In case this is a removed local track, clean-up | ||
// the (unified) m-line and make sure it's 'recvonly' or | ||
// 'inactive'. | ||
delete uLine.msid; | ||
delete uLine.sources; | ||
delete uLine.ssrcGroups; | ||
if (!uLine.direction | ||
|| uLine.direction === 'sendrecv') | ||
uLine.direction = 'recvonly'; | ||
else if (uLine.direction === 'sendonly') | ||
uLine.direction = 'inactive'; | ||
} else { | ||
// This is an (unified) m-line/channel that contains a local | ||
// track (sendrecv or sendonly channel) or it's a unified | ||
// recvonly m-line/channel. In either case, since we're | ||
// going from PlanB -> Unified Plan this m-line MUST | ||
// exist in the cached answer. | ||
} | ||
session.media.push(uLine); | ||
if (typeof uLine.mid === 'string') { | ||
// inactive lines don't/may not have an mid. | ||
mids.push(uLine.mid); | ||
} | ||
} | ||
} else { | ||
// SDP offer/answer (and the JSEP spec) forbids removing an m-section | ||
// under any circumstances. If we are no longer interested in sending a | ||
// track, we just remove the msid and ssrc attributes and set it to | ||
// either a=recvonly (as the reofferer, we must use recvonly if the | ||
// other side was previously sending on the m-section, but we can also | ||
// leave the possibility open if it wasn't previously in use), or | ||
// a=inactive. | ||
if (typeof cached !== 'undefined' && | ||
typeof cached.media !== 'undefined' && | ||
Array.isArray(cached.media)) { | ||
cached.media.forEach(function(uLine) { | ||
midToString(uLine); | ||
mids.push(uLine.mid); | ||
if (typeof mid2ul[uLine.mid] !== 'undefined') { | ||
session.media.push(mid2ul[uLine.mid]); | ||
} else { | ||
delete uLine.msid; | ||
delete uLine.sources; | ||
delete uLine.ssrcGroups; | ||
if (!uLine.direction | ||
|| uLine.direction === 'sendrecv') | ||
uLine.direction = 'recvonly'; | ||
if (!uLine.direction | ||
|| uLine.direction === 'sendonly') | ||
uLine.direction = 'inactive'; | ||
session.media.push(uLine); | ||
newMline.mid = currentDesc.media.length.toString(); | ||
newMline.direction = 'sendonly'; | ||
addSourcesToMline(newMline, ssrc, ssrc2group, mLine.sources); | ||
currentDesc.media.push(newMline); | ||
} | ||
}); | ||
} | ||
}); | ||
session.media = currentDesc ? currentDesc.media : Object.values(media); | ||
const mids = []; | ||
// Add all the remaining (new) m-lines of the transformed SDP. | ||
Object.keys(mid2ul).forEach(function(mid) { | ||
if (mids.indexOf(mid) === -1) { | ||
mids.push(mid); | ||
if (mid2ul[mid].direction === 'recvonly') { | ||
// This is a remote recvonly channel. Add its SSRC to the | ||
// appropriate sendrecv or sendonly channel. | ||
// TODO(gp) what if we don't have sendrecv/sendonly | ||
// channel? | ||
session.media.forEach(mLine => { | ||
mids.push(mLine.mid); | ||
}); | ||
session.media.some(function (uLine) { | ||
if ((uLine.direction === 'sendrecv' || | ||
uLine.direction === 'sendonly') && | ||
uLine.type === mid2ul[mid].type) { | ||
// mid2ul[mid] shouldn't have any ssrc-groups | ||
Object.keys(mid2ul[mid].sources).forEach( | ||
function (ssrc) { | ||
uLine.sources[ssrc] = | ||
mid2ul[mid].sources[ssrc]; | ||
}); | ||
return true; | ||
} | ||
}); | ||
} else { | ||
session.media.push(mid2ul[mid]); | ||
} | ||
// We regenerate the BUNDLE group (since we regenerated the mids) | ||
session.groups.forEach(group => { | ||
if (group.type === 'BUNDLE') { | ||
group.mids = mids.join(' '); | ||
} | ||
}); | ||
} | ||
// After we have constructed the Plan Unified m-lines we can figure out | ||
// where (in which m-line) to place the 'recvonly SSRCs'. | ||
// Note: we assume here that we are the answerer in the O/A, so any offers | ||
// which we translate come from the remote side, while answers are local | ||
// (and so our last local description is cached as an 'answer'). | ||
["audio", "video"].forEach(function (type) { | ||
if (!session || !session.media || !Array.isArray(session.media)) | ||
return; | ||
// msid semantic | ||
session.msidSemantic = { | ||
semantic: 'WMS', | ||
token: '*' | ||
}; | ||
var idx = null; | ||
if (Object.keys(recvonlySsrcs[type]).length > 0) { | ||
idx = self.getFirstSendingIndexFromAnswer(type); | ||
if (idx === null){ | ||
// If this is the first offer we receive, we don't have a | ||
// cached answer. Assume that we will be sending media using | ||
// the first m-line for each media type. | ||
// Increment the session version every time. | ||
session.origin.sessionVersion++; | ||
const resultSdp = transform.write(session); | ||
for (var i = 0; i < session.media.length; i++) { | ||
if (session.media[i].type === type) { | ||
idx = i; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
if (idx && session.media.length > idx) { | ||
var mLine = session.media[idx]; | ||
Object.keys(recvonlySsrcs[type]).forEach(function(ssrc) { | ||
if (mLine.sources && mLine.sources[ssrc]) { | ||
console.warn("Replacing an existing SSRC."); | ||
} | ||
if (!mLine.sources) { | ||
mLine.sources = {}; | ||
} | ||
mLine.sources[ssrc] = recvonlySsrcs[type][ssrc]; | ||
}); | ||
} | ||
}); | ||
// We regenerate the BUNDLE group (since we regenerated the mids) | ||
session.groups.some(function(group) { | ||
if (group.type === 'BUNDLE') { | ||
group.mids = mids.join(' '); | ||
return true; | ||
} | ||
}); | ||
// msid semantic | ||
session.msidSemantic = { | ||
semantic: 'WMS', | ||
token: '*' | ||
}; | ||
var resStr = transform.write(session); | ||
// Cache the transformed SDP (Unified Plan) for later re-use in this | ||
// function. | ||
this.cache[desc.type] = resStr; | ||
return new RTCSessionDescription({ | ||
type: desc.type, | ||
sdp: resStr | ||
}); | ||
//#endregion | ||
}; | ||
/** | ||
* Maps the direction strings to their binary representation. The binary | ||
* representation of the directions will contain only 2 bits. The least | ||
* significant bit will indicate the receiving direction and the other bit will | ||
* indicate the sending direction. | ||
* | ||
* @type {Map<string, number>} | ||
*/ | ||
var directionMasks = { | ||
'inactive': 0, // 00 | ||
'recvonly': 1, // 01 | ||
'sendonly': 2, // 10 | ||
'sendrecv': 3 // 11 | ||
}; | ||
/** | ||
* Parses a number into direction string. | ||
* | ||
* @param {number} direction - The number to be parsed. | ||
* @returns {string} - The parsed direction string. | ||
*/ | ||
function parseDirection(direction) { // eslint-disable-line no-unused-vars | ||
// Filter all other bits except the 2 less significant. | ||
var directionMask = direction & 3; | ||
switch (directionMask) { | ||
case 0: | ||
return 'inactive'; | ||
case 1: | ||
return 'recvonly'; | ||
case 2: | ||
return 'sendonly'; | ||
case 3: | ||
return 'sendrecv'; | ||
return new RTCSessionDescription({ | ||
type: description.type, | ||
sdp: resultSdp | ||
}); | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
/* Copyright @ 2015 Atlassian Pty Ltd | ||
/* Copyright @ 2015 - Present, 8x8 Inc | ||
* | ||
@@ -16,98 +16,93 @@ * Licensed under the Apache License, Version 2.0 (the "License"); | ||
var transform = require('sdp-transform'); | ||
import transform from 'sdp-transform'; | ||
exports.write = function(session, opts) { | ||
/** | ||
* Rewrites the source information in the way sdp-transform expects. | ||
* Source information is split into multiple ssrc objects each containing | ||
* an id, attribute and value. | ||
* @param {Object} media - media description to be modified. | ||
* @returns {void} | ||
*/ | ||
const write = function(session, opts) { | ||
if (typeof session !== 'undefined' && typeof session.media !== 'undefined' && Array.isArray(session.media)) { | ||
session.media.forEach(mLine => { | ||
if (mLine.sources && mLine.sources.length) { | ||
mLine.ssrcs = []; | ||
mLine.sources.forEach(source => { | ||
Object.keys(source).forEach(attribute => { | ||
if (attribute === 'id') { | ||
return; | ||
} | ||
mLine.ssrcs.push({ | ||
id: source.id, | ||
attribute, | ||
value: source[attribute] | ||
}); | ||
}); | ||
}); | ||
delete mLine.sources; | ||
} | ||
if (typeof session !== 'undefined' && | ||
typeof session.media !== 'undefined' && | ||
Array.isArray(session.media)) { | ||
session.media.forEach(function (mLine) { | ||
// expand sources to ssrcs | ||
if (typeof mLine.sources !== 'undefined' && | ||
Object.keys(mLine.sources).length !== 0) { | ||
mLine.ssrcs = []; | ||
Object.keys(mLine.sources).forEach(function (ssrc) { | ||
var source = mLine.sources[ssrc]; | ||
Object.keys(source).forEach(function (attribute) { | ||
mLine.ssrcs.push({ | ||
id: ssrc, | ||
attribute: attribute, | ||
value: source[attribute] | ||
}); | ||
}); | ||
}); | ||
delete mLine.sources; | ||
} | ||
// join ssrcs in ssrc groups | ||
if (typeof mLine.ssrcGroups !== 'undefined' && | ||
Array.isArray(mLine.ssrcGroups)) { | ||
mLine.ssrcGroups.forEach(function (ssrcGroup) { | ||
if (typeof ssrcGroup.ssrcs !== 'undefined' && | ||
Array.isArray(ssrcGroup.ssrcs)) { | ||
ssrcGroup.ssrcs = ssrcGroup.ssrcs.join(' '); | ||
// join ssrcs in ssrc groups | ||
if (mLine.ssrcGroups && mLine.ssrcGroups.length) { | ||
mLine.ssrcGroups.forEach(ssrcGroup => { | ||
if (typeof ssrcGroup.ssrcs !== 'undefined' | ||
&& Array.isArray(ssrcGroup.ssrcs)) { | ||
ssrcGroup.ssrcs = ssrcGroup.ssrcs.join(' '); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
// join group mids | ||
if (typeof session !== 'undefined' && | ||
typeof session.groups !== 'undefined' && Array.isArray(session.groups)) { | ||
session.groups.forEach(function (g) { | ||
if (typeof g.mids !== 'undefined' && Array.isArray(g.mids)) { | ||
g.mids = g.mids.join(' '); | ||
} | ||
}); | ||
} | ||
return transform.write(session, opts); | ||
return transform.write(session, opts); | ||
}; | ||
exports.parse = function(sdp) { | ||
var session = transform.parse(sdp); | ||
/** | ||
* Rewrites the source information that we get from sdp-transform. | ||
* All the ssrc lines with different attributes that belong to the | ||
* same ssrc are grouped into a single soure object with multiple key value pairs. | ||
* @param {Object} media - media description to be modified. | ||
* @returns {void} | ||
*/ | ||
const parse = function(sdp) { | ||
const session = transform.parse(sdp); | ||
if (typeof session !== 'undefined' && typeof session.media !== 'undefined' && | ||
Array.isArray(session.media)) { | ||
if (typeof session !== 'undefined' && typeof session.media !== 'undefined' && Array.isArray(session.media)) { | ||
session.media.forEach(mLine => { | ||
// group sources attributes by ssrc | ||
if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) { | ||
mLine.sources = []; | ||
mLine.ssrcs.forEach(ssrc => { | ||
const found = mLine.sources.findIndex(source => source.id === ssrc.id); | ||
session.media.forEach(function (mLine) { | ||
// group sources attributes by ssrc | ||
if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) { | ||
mLine.sources = {}; | ||
mLine.ssrcs.forEach(function (ssrc) { | ||
if (!mLine.sources[ssrc.id]) | ||
mLine.sources[ssrc.id] = {}; | ||
mLine.sources[ssrc.id][ssrc.attribute] = ssrc.value; | ||
}); | ||
if (found > -1) { | ||
mLine.sources[found][ssrc.attribute] = ssrc.value; | ||
} else { | ||
const src = { id: ssrc.id }; | ||
delete mLine.ssrcs; | ||
} | ||
src[ssrc.attribute] = ssrc.value; | ||
mLine.sources.push(src); | ||
} | ||
}); | ||
delete mLine.ssrcs; | ||
} | ||
// split ssrcs in ssrc groups | ||
if (typeof mLine.ssrcGroups !== 'undefined' && | ||
Array.isArray(mLine.ssrcGroups)) { | ||
mLine.ssrcGroups.forEach(function (ssrcGroup) { | ||
if (typeof ssrcGroup.ssrcs === 'string') { | ||
ssrcGroup.ssrcs = ssrcGroup.ssrcs.split(' '); | ||
// split ssrcs in ssrc groups | ||
if (typeof mLine.ssrcGroups !== 'undefined' && Array.isArray(mLine.ssrcGroups)) { | ||
mLine.ssrcGroups.forEach(ssrcGroup => { | ||
if (typeof ssrcGroup.ssrcs === 'string') { | ||
ssrcGroup.ssrcs = ssrcGroup.ssrcs.split(' '); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
// split group mids | ||
if (typeof session !== 'undefined' && | ||
typeof session.groups !== 'undefined' && Array.isArray(session.groups)) { | ||
}); | ||
} | ||
session.groups.forEach(function (g) { | ||
if (typeof g.mids === 'string') { | ||
g.mids = g.mids.split(' '); | ||
} | ||
}); | ||
} | ||
return session; | ||
}; | ||
return session; | ||
export default { | ||
write, | ||
parse | ||
}; | ||
@@ -5,3 +5,3 @@ { | ||
"author": "", | ||
"version": "0.1.14", | ||
"version": "1.0.0", | ||
"stability": "unstable", | ||
@@ -12,2 +12,3 @@ "repository": { | ||
}, | ||
"type": "module", | ||
"keywords": [ | ||
@@ -21,6 +22,6 @@ "sdp", | ||
"scripts": { | ||
"test": "node $NODE_DEBUG_OPTION test/sdp_interop.js", | ||
"test": "node $NODE_DEBUG_OPTION --experimental-modules test/sdp_interop.js", | ||
"coverage": "jscoverage lib && SDP_TRANSFORM_COV=1 nodeunit --reporter=lcov test", | ||
"coveralls": "npm run coverage | coveralls", | ||
"lint": "./node_modules/.bin/jshint .", | ||
"lint": "eslint .", | ||
"validate": "npm ls" | ||
@@ -32,10 +33,16 @@ }, | ||
"devDependencies": { | ||
"coveralls": "^2.11.1", | ||
"jscoverage": "^0.5.5", | ||
"jshint": "2.8.0", | ||
"precommit-hook": "^3.0.0", | ||
"qunit-cli": "^0.2.0" | ||
"babel-eslint": "10.1.0", | ||
"coveralls": "3.0.11", | ||
"eslint": "6.8.0", | ||
"eslint-config-jitsi": "github:jitsi/eslint-config-jitsi#1.0.0", | ||
"eslint-plugin-flowtype": "4.7.0", | ||
"eslint-plugin-import": "2.20.2", | ||
"jscoverage": "0.6.0", | ||
"lodash.clonedeep": "4.5.0", | ||
"precommit-hook": "3.0.0", | ||
"qunit-cli": "0.2.0" | ||
}, | ||
"pre-commit": [ | ||
"lint" | ||
"lint", | ||
"test" | ||
], | ||
@@ -42,0 +49,0 @@ "bugs": { |
@@ -179,8 +179,4 @@ [![Build Status](https://travis-ci.org/jitsi/sdp-interop.svg?branch=master)](https://travis-ci.org/jitsi/sdp-interop) | ||
We solved this issue by caching both the most recent Unified Plan offer and the | ||
most recent Unified Plan answer. When we go from Plan B to Unified Plan we use | ||
the cached Unified Plan offer/answer and add the missing information from | ||
there. You can see | ||
[here](https://github.com/jitsi/sdp-interop/blob/d4569a12875a7180004726633793430eccd7f47b/lib/interop.js#L175) | ||
how we do this exactly. | ||
We solved this issue by passing the current description set on the peerconnection | ||
when converting a Plan B offer to a Unified Plan offer. | ||
@@ -191,10 +187,8 @@ Another soft limitation (in the sense that it can be removed given enough | ||
One last soft limitation is that we have currently tested the interoperability | ||
layer only when Firefox answers a call and not when it offers one because in | ||
our architecture endpoints always get invited to join a call and never offer | ||
one. This is tracked in [issue #4](https://github.com/jitsi/sdp-interop/issues/4). | ||
Firefox can also be the offerer, i.e., it can create an offer and send it to another | ||
client for establishing a peer-to-peer connection. | ||
## Copyright notice | ||
Copyright @ 2015 Atlassian Pty Ltd | ||
Copyright @ 2015 - Present, 8x8 Inc | ||
@@ -201,0 +195,0 @@ Licensed under the Apache License, Version 2.0 (the "License"); |
@@ -1,11 +0,7 @@ | ||
var Interop = require('../').Interop; | ||
var fs = require('fs'); | ||
import { Interop } from '../lib/interop.js'; | ||
import fs from 'fs'; | ||
import QUnit from 'qunit-cli'; | ||
if (typeof QUnit == 'undefined') { | ||
QUnit = require('qunit-cli'); | ||
QUnit.load(); | ||
QUnit.load() | ||
interop = require('..'); | ||
}; | ||
global.RTCSessionDescription = function (desc) { | ||
@@ -16,8 +12,2 @@ this.type = desc.type; | ||
global.RTCIceCandidate = function (cand) { | ||
this.candidate = cand.candidate; | ||
this.sdpMLineIndex = cand.sdpMLineIndex; | ||
this.sdpMid = cand.sdpMid; | ||
} | ||
var dumpSDP = function (description) { | ||
@@ -102,7 +92,7 @@ if (typeof description === 'undefined' || description === null) { | ||
"v=0\r\n\ | ||
o=- 6352417452822806569 2 IN IP4 127.0.0.1\r\n\ | ||
o=- 6352417452822806569 3 IN IP4 127.0.0.1\r\n\ | ||
s=-\r\n\ | ||
t=0 0\r\n\ | ||
a=msid-semantic: WMS *\r\n\ | ||
a=group:BUNDLE audio-3393882360 video-1733429841\r\n\ | ||
a=group:BUNDLE 0 1\r\n\ | ||
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 126\r\n\ | ||
@@ -125,3 +115,3 @@ c=IN IP4 0.0.0.0\r\n\ | ||
a=setup:actpass\r\n\ | ||
a=mid:audio-3393882360\r\n\ | ||
a=mid:0\r\n\ | ||
a=msid:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c 22345512-82de-4e55-b205-967e0249e8e0\r\n\ | ||
@@ -134,2 +124,3 @@ a=maxptime:60\r\n\ | ||
a=ssrc:3393882360 cname:5YcASuDc3X86mu+d\r\n\ | ||
a=ssrc:3393882360 msid:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c 22345512-82de-4e55-b205-967e0249e8e0\r\n\ | ||
a=ssrc:3393882360 mslabel:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c\r\n\ | ||
@@ -154,3 +145,3 @@ a=ssrc:3393882360 label:22345512-82de-4e55-b205-967e0249e8e0\r\n\ | ||
a=setup:actpass\r\n\ | ||
a=mid:video-1733429841\r\n\ | ||
a=mid:1\r\n\ | ||
a=msid:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c 9203939c-25cf-4d60-82c2-d25b19350926\r\n\ | ||
@@ -161,10 +152,12 @@ a=sendrecv\r\n\ | ||
a=fingerprint:sha-256 58:E0:FE:56:6A:8C:5A:AD:71:5B:A0:52:47:27:60:66:27:53:EC:B6:F3:03:A8:4B:9B:30:28:62:29:49:C6:73\r\n\ | ||
a=ssrc:2560713622 cname:5YcASuDc3X86mu+d\r\n\ | ||
a=ssrc:2560713622 msid:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c 9203939c-25cf-4d60-82c2-d25b19350926\r\n\ | ||
a=ssrc:2560713622 mslabel:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c\r\n\ | ||
a=ssrc:2560713622 label:9203939c-25cf-4d60-82c2-d25b19350926\r\n\ | ||
a=ssrc:1733429841 cname:5YcASuDc3X86mu+d\r\n\ | ||
a=ssrc:1733429841 msid:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c 9203939c-25cf-4d60-82c2-d25b19350926\r\n\ | ||
a=ssrc:1733429841 mslabel:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c\r\n\ | ||
a=ssrc:1733429841 label:9203939c-25cf-4d60-82c2-d25b19350926\r\n\ | ||
a=ssrc:2560713622 cname:5YcASuDc3X86mu+d\r\n\ | ||
a=ssrc:2560713622 mslabel:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c\r\n\ | ||
a=ssrc:2560713622 label:9203939c-25cf-4d60-82c2-d25b19350926\r\n\ | ||
a=ssrc-group:FID 2560713622 1733429841\r\n\ | ||
a=rtcp-mux\r\n" | ||
a=rtcp-mux\r\n"; | ||
@@ -268,7 +261,7 @@ var interop = new Interop(); | ||
"v=0\r\n\ | ||
o=- 6352417452822806569 2 IN IP4 127.0.0.1\r\n\ | ||
o=- 6352417452822806569 3 IN IP4 127.0.0.1\r\n\ | ||
s=-\r\n\ | ||
t=0 0\r\n\ | ||
a=msid-semantic: WMS *\r\n\ | ||
a=group:BUNDLE audio-2998362345 audio-3393882360 video-624578865 video-1733429841\r\n\ | ||
a=group:BUNDLE 0 1 2 3\r\n\ | ||
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 126\r\n\ | ||
@@ -291,4 +284,4 @@ c=IN IP4 0.0.0.0\r\n\ | ||
a=setup:actpass\r\n\ | ||
a=mid:audio-2998362345\r\n\ | ||
a=msid:0ec45b31-e98d-49fa-b695-7631e004843a 96a45cea-7b24-401f-b12b-92bead3bf181\r\n\ | ||
a=mid:0\r\n\ | ||
a=msid:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c 22345512-82de-4e55-b205-967e0249e8e0\r\n\ | ||
a=maxptime:60\r\n\ | ||
@@ -299,5 +292,6 @@ a=sendrecv\r\n\ | ||
a=fingerprint:sha-256 58:E0:FE:56:6A:8C:5A:AD:71:5B:A0:52:47:27:60:66:27:53:EC:B6:F3:03:A8:4B:9B:30:28:62:29:49:C6:73\r\n\ | ||
a=ssrc:2998362345 cname:XvUdN+mQ3KWuNJNu\r\n\ | ||
a=ssrc:2998362345 mslabel:0ec45b31-e98d-49fa-b695-7631e004843a\r\n\ | ||
a=ssrc:2998362345 label:96a45cea-7b24-401f-b12b-92bead3bf181\r\n\ | ||
a=ssrc:3393882360 cname:5YcASuDc3X86mu+d\r\n\ | ||
a=ssrc:3393882360 msid:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c 22345512-82de-4e55-b205-967e0249e8e0\r\n\ | ||
a=ssrc:3393882360 mslabel:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c\r\n\ | ||
a=ssrc:3393882360 label:22345512-82de-4e55-b205-967e0249e8e0\r\n\ | ||
a=rtcp-mux\r\n\ | ||
@@ -321,12 +315,13 @@ m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 126\r\n\ | ||
a=setup:actpass\r\n\ | ||
a=mid:audio-3393882360\r\n\ | ||
a=msid:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c 22345512-82de-4e55-b205-967e0249e8e0\r\n\ | ||
a=mid:1\r\n\ | ||
a=msid:0ec45b31-e98d-49fa-b695-7631e004843a 96a45cea-7b24-401f-b12b-92bead3bf181\r\n\ | ||
a=maxptime:60\r\n\ | ||
a=sendrecv\r\n\ | ||
a=sendonly\r\n\ | ||
a=ice-ufrag:xHOGnBsKDPCmHB5t\r\n\ | ||
a=ice-pwd:qpnbhhoyeTrypBkX5F1u338T\r\n\ | ||
a=fingerprint:sha-256 58:E0:FE:56:6A:8C:5A:AD:71:5B:A0:52:47:27:60:66:27:53:EC:B6:F3:03:A8:4B:9B:30:28:62:29:49:C6:73\r\n\ | ||
a=ssrc:3393882360 cname:5YcASuDc3X86mu+d\r\n\ | ||
a=ssrc:3393882360 mslabel:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c\r\n\ | ||
a=ssrc:3393882360 label:22345512-82de-4e55-b205-967e0249e8e0\r\n\ | ||
a=ssrc:2998362345 cname:XvUdN+mQ3KWuNJNu\r\n\ | ||
a=ssrc:2998362345 msid:0ec45b31-e98d-49fa-b695-7631e004843a 96a45cea-7b24-401f-b12b-92bead3bf181\r\n\ | ||
a=ssrc:2998362345 mslabel:0ec45b31-e98d-49fa-b695-7631e004843a\r\n\ | ||
a=ssrc:2998362345 label:96a45cea-7b24-401f-b12b-92bead3bf181\r\n\ | ||
a=rtcp-mux\r\n\ | ||
@@ -349,4 +344,4 @@ m=video 9 UDP/TLS/RTP/SAVPF 100 116 117 96\r\n\ | ||
a=setup:actpass\r\n\ | ||
a=mid:video-624578865\r\n\ | ||
a=msid:0ec45b31-e98d-49fa-b695-7631e004843a 6f961540-d5ee-46da-a5b7-b42b97211905\r\n\ | ||
a=mid:2\r\n\ | ||
a=msid:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c 9203939c-25cf-4d60-82c2-d25b19350926\r\n\ | ||
a=sendrecv\r\n\ | ||
@@ -356,9 +351,11 @@ a=ice-ufrag:xHOGnBsKDPCmHB5t\r\n\ | ||
a=fingerprint:sha-256 58:E0:FE:56:6A:8C:5A:AD:71:5B:A0:52:47:27:60:66:27:53:EC:B6:F3:03:A8:4B:9B:30:28:62:29:49:C6:73\r\n\ | ||
a=ssrc:624578865 cname:XvUdN+mQ3KWuNJNu\r\n\ | ||
a=ssrc:624578865 mslabel:0ec45b31-e98d-49fa-b695-7631e004843a\r\n\ | ||
a=ssrc:624578865 label:6f961540-d5ee-46da-a5b7-b42b97211905\r\n\ | ||
a=ssrc:3792658351 cname:XvUdN+mQ3KWuNJNu\r\n\ | ||
a=ssrc:3792658351 mslabel:0ec45b31-e98d-49fa-b695-7631e004843a\r\n\ | ||
a=ssrc:3792658351 label:6f961540-d5ee-46da-a5b7-b42b97211905\r\n\ | ||
a=ssrc-group:FID 3792658351 624578865\r\n\ | ||
a=ssrc:2560713622 cname:5YcASuDc3X86mu+d\r\n\ | ||
a=ssrc:2560713622 msid:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c 9203939c-25cf-4d60-82c2-d25b19350926\r\n\ | ||
a=ssrc:2560713622 mslabel:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c\r\n\ | ||
a=ssrc:2560713622 label:9203939c-25cf-4d60-82c2-d25b19350926\r\n\ | ||
a=ssrc:1733429841 cname:5YcASuDc3X86mu+d\r\n\ | ||
a=ssrc:1733429841 msid:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c 9203939c-25cf-4d60-82c2-d25b19350926\r\n\ | ||
a=ssrc:1733429841 mslabel:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c\r\n\ | ||
a=ssrc:1733429841 label:9203939c-25cf-4d60-82c2-d25b19350926\r\n\ | ||
a=ssrc-group:FID 2560713622 1733429841\r\n\ | ||
a=rtcp-mux\r\n\ | ||
@@ -381,15 +378,17 @@ m=video 9 UDP/TLS/RTP/SAVPF 100 116 117 96\r\n\ | ||
a=setup:actpass\r\n\ | ||
a=mid:video-1733429841\r\n\ | ||
a=msid:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c 9203939c-25cf-4d60-82c2-d25b19350926\r\n\ | ||
a=sendrecv\r\n\ | ||
a=mid:3\r\n\ | ||
a=msid:0ec45b31-e98d-49fa-b695-7631e004843a 6f961540-d5ee-46da-a5b7-b42b97211905\r\n\ | ||
a=sendonly\r\n\ | ||
a=ice-ufrag:xHOGnBsKDPCmHB5t\r\n\ | ||
a=ice-pwd:qpnbhhoyeTrypBkX5F1u338T\r\n\ | ||
a=fingerprint:sha-256 58:E0:FE:56:6A:8C:5A:AD:71:5B:A0:52:47:27:60:66:27:53:EC:B6:F3:03:A8:4B:9B:30:28:62:29:49:C6:73\r\n\ | ||
a=ssrc:1733429841 cname:5YcASuDc3X86mu+d\r\n\ | ||
a=ssrc:1733429841 mslabel:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c\r\n\ | ||
a=ssrc:1733429841 label:9203939c-25cf-4d60-82c2-d25b19350926\r\n\ | ||
a=ssrc:2560713622 cname:5YcASuDc3X86mu+d\r\n\ | ||
a=ssrc:2560713622 mslabel:nnnwYrPTpGmyoJX5GFHMVv42y1ZthbnCx26c\r\n\ | ||
a=ssrc:2560713622 label:9203939c-25cf-4d60-82c2-d25b19350926\r\n\ | ||
a=ssrc-group:FID 2560713622 1733429841\r\n\ | ||
a=ssrc:3792658351 cname:XvUdN+mQ3KWuNJNu\r\n\ | ||
a=ssrc:3792658351 msid:0ec45b31-e98d-49fa-b695-7631e004843a 6f961540-d5ee-46da-a5b7-b42b97211905\r\n\ | ||
a=ssrc:3792658351 mslabel:0ec45b31-e98d-49fa-b695-7631e004843a\r\n\ | ||
a=ssrc:3792658351 label:6f961540-d5ee-46da-a5b7-b42b97211905\r\n\ | ||
a=ssrc:624578865 cname:XvUdN+mQ3KWuNJNu\r\n\ | ||
a=ssrc:624578865 msid:0ec45b31-e98d-49fa-b695-7631e004843a 6f961540-d5ee-46da-a5b7-b42b97211905\r\n\ | ||
a=ssrc:624578865 mslabel:0ec45b31-e98d-49fa-b695-7631e004843a\r\n\ | ||
a=ssrc:624578865 label:6f961540-d5ee-46da-a5b7-b42b97211905\r\n\ | ||
a=ssrc-group:FID 3792658351 624578865\r\n\ | ||
a=rtcp-mux\r\n" | ||
@@ -407,66 +406,2 @@ | ||
"Not expected Unified Plan output") | ||
/* #region Check Unified Plan candidates */ | ||
var candUnifiedPlan = new RTCIceCandidate ({ | ||
"candidate" : "candidate:11111111 1 udp 22222222 10.0.0.1 2345 typ host generation 0", | ||
"sdpMLineIndex" : 0, | ||
"sdpMid" : "audio-2998362345" | ||
}); | ||
var candPlanB = interop.candidateToPlanB (candUnifiedPlan); | ||
assert.equal(candPlanB.candidate, candUnifiedPlan.candidate, "candidate arg not matching"); | ||
assert.equal(candPlanB.sdpMid, "audio", "sdpMid arg not matching"); | ||
assert.equal(candPlanB.sdpMLineIndex, 0, "sdpMLineIndex arg not matching"); | ||
var candUnifiedPlan = new RTCIceCandidate ({ | ||
"candidate" : "candidate:11111111 1 udp 22222222 10.0.0.1 2345 typ host generation 0", | ||
"sdpMLineIndex" : 1, | ||
"sdpMid" : "audio-3393882360" | ||
}); | ||
var candPlanB = interop.candidateToPlanB (candUnifiedPlan); | ||
assert.equal(candPlanB.candidate, candUnifiedPlan.candidate, "candidate arg not matching"); | ||
assert.equal(candPlanB.sdpMid, "audio", "sdpMid arg not matching"); | ||
assert.equal(candPlanB.sdpMLineIndex, 0, "sdpMLineIndex arg not matching"); | ||
var candUnifiedPlan = new RTCIceCandidate ({ | ||
"candidate" : "candidate:11111111 1 udp 22222222 10.0.0.1 2345 typ host generation 0", | ||
"sdpMLineIndex" : 2, | ||
"sdpMid" : "video-624578865" | ||
}); | ||
var candPlanB = interop.candidateToPlanB (candUnifiedPlan); | ||
assert.equal(candPlanB.candidate, candUnifiedPlan.candidate, "candidate arg not matching"); | ||
assert.equal(candPlanB.sdpMid, "video", "sdpMid arg not matching"); | ||
assert.equal(candPlanB.sdpMLineIndex, 1, "sdpMLineIndex arg not matching"); | ||
var candUnifiedPlan = new RTCIceCandidate ({ | ||
"candidate" : "candidate:11111111 1 udp 22222222 10.0.0.1 2345 typ host generation 0", | ||
"sdpMLineIndex" : 3, | ||
"sdpMid" : "video-1733429841" | ||
}); | ||
var candPlanB = interop.candidateToPlanB (candUnifiedPlan); | ||
assert.equal(candPlanB.candidate, candUnifiedPlan.candidate, "candidate arg not matching"); | ||
assert.equal(candPlanB.sdpMid, "video", "sdpMid arg not matching"); | ||
assert.equal(candPlanB.sdpMLineIndex, 1, "sdpMLineIndex arg not matching"); | ||
/* #endregion */ | ||
/* #region Check Plan B candidates */ | ||
var candPlanB = new RTCIceCandidate ({ | ||
"candidate" : "candidate:11111111 1 udp 22222222 10.0.0.1 2345 typ host generation 0", | ||
"sdpMLineIndex" : 0, | ||
"sdpMid" : "audio" | ||
}); | ||
var candUnifiedPlan = interop.candidateToUnifiedPlan (candPlanB); | ||
assert.equal(candUnifiedPlan.candidate, candPlanB.candidate, "candidate arg not matching"); | ||
assert.equal(candUnifiedPlan.sdpMid, "audio", "sdpMid arg not matching"); | ||
assert.equal(candUnifiedPlan.sdpMLineIndex, 0, "sdpMLineIndex arg not matching"); | ||
var candPlanB = new RTCIceCandidate ({ | ||
"candidate" : "candidate:11111111 1 udp 22222222 10.0.0.1 2345 typ host generation 0", | ||
"sdpMLineIndex" : 1, | ||
"sdpMid" : "video" | ||
}); | ||
var candUnifiedPlan = interop.candidateToUnifiedPlan (candPlanB); | ||
assert.equal(candUnifiedPlan.candidate, candPlanB.candidate, "candidate arg not matching"); | ||
assert.equal(candUnifiedPlan.sdpMid, "video", "sdpMid arg not matching"); | ||
assert.equal(candUnifiedPlan.sdpMLineIndex, 2, "sdpMLineIndex arg not matching"); | ||
/* #endregion */ | ||
}); | ||
@@ -518,7 +453,7 @@ | ||
"v=0\r\n\ | ||
o=- 6352417452822806569 2 IN IP4 127.0.0.1\r\n\ | ||
o=- 6352417452822806569 3 IN IP4 127.0.0.1\r\n\ | ||
s=-\r\n\ | ||
t=0 0\r\n\ | ||
a=msid-semantic: WMS *\r\n\ | ||
a=group:BUNDLE audio-1001 video-2001\r\n\ | ||
a=group:BUNDLE 0 1\r\n\ | ||
m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n\ | ||
@@ -529,3 +464,3 @@ c=IN IP4 0.0.0.0\r\n\ | ||
a=setup:actpass\r\n\ | ||
a=mid:audio-1001\r\n\ | ||
a=mid:0\r\n\ | ||
a=msid:MS-0 MST-0_0\r\n\ | ||
@@ -537,2 +472,3 @@ a=sendonly\r\n\ | ||
a=ssrc:1001 cname:CN-0\r\n\ | ||
a=ssrc:1001 msid:MS-0 MST-0_0\r\n\ | ||
a=ssrc:1001 mslabel:MS-0\r\n\ | ||
@@ -546,3 +482,3 @@ a=ssrc:1001 label:MST-0_0\r\n\ | ||
a=setup:actpass\r\n\ | ||
a=mid:video-2001\r\n\ | ||
a=mid:1\r\n\ | ||
a=msid:MS-0 9203939c-25cf-4d60-82c2-d25b19350926\r\n\ | ||
@@ -554,2 +490,3 @@ a=sendonly\r\n\ | ||
a=ssrc:2001 cname:CN-0\r\n\ | ||
a=ssrc:2001 msid:MS-0 9203939c-25cf-4d60-82c2-d25b19350926\r\n\ | ||
a=ssrc:2001 mslabel:MS-0\r\n\ | ||
@@ -580,9 +517,9 @@ a=ssrc:2001 label:9203939c-25cf-4d60-82c2-d25b19350926\r\n\ | ||
a=msid-semantic: WMS *\r\n\ | ||
a=group:BUNDLE video-65477720 video-774581929\r\n\ | ||
a=group:BUNDLE 0 1 2 3\r\n\ | ||
m=audio 0 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 126\r\n\ | ||
a=inactive\r\n\ | ||
a=mid:audio-2331169307\r\n\ | ||
a=mid:0\r\n\ | ||
m=audio 0 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 126\r\n\ | ||
a=inactive\r\n\ | ||
a=mid:audio-3362868299\r\n\ | ||
a=mid:1\r\n\ | ||
m=video 1 UDP/TLS/RTP/SAVPF 100\r\n\ | ||
@@ -598,3 +535,3 @@ b=AS:2000\r\n\ | ||
a=setup:active\r\n\ | ||
a=mid:video-65477720\r\n\ | ||
a=mid:2\r\n\ | ||
a=recvonly\r\n\ | ||
@@ -616,3 +553,3 @@ a=rtcp-mux\r\n\ | ||
a=setup:active\r\n\ | ||
a=mid:video-774581929\r\n\ | ||
a=mid:3\r\n\ | ||
a=recvonly\r\n\ | ||
@@ -652,3 +589,2 @@ a=rtcp-mux\r\n\ | ||
a=fingerprint:sha-256 E7:70:CE:58:6A:CC:77:B0:B4:4B:F2:BC:7E:89:0D:69:E3:90:F3:7A:11:78:B1:5A:CD:E6:41:19:14:EB:56:49\r\n\ | ||
a=ssrc:3423627266 cname:user1483941637@host-3c4150dc\r\n\ | ||
a=ssrc:3850339357 cname:user1483941637@host-3c4150dc\r\n\ | ||
@@ -678,6 +614,3 @@ a=rtcp-mux\r\n" | ||
a=msid-semantic: WMS *\r\n\ | ||
a=group:BUNDLE audio-3362868299 video-774581929\r\n\ | ||
m=audio 0 UDP/TLS/RTP/SAVPF 111 0\r\n\ | ||
a=inactive\r\n\ | ||
a=mid:audio-2331169307\r\n\ | ||
a=group:BUNDLE 0 1 2 3\r\n\ | ||
m=audio 1 UDP/TLS/RTP/SAVPF 111 0\r\n\ | ||
@@ -689,3 +622,3 @@ a=rtcp:9 IN IP4 0.0.0.0\r\n\ | ||
a=setup:active\r\n\ | ||
a=mid:audio-3362868299\r\n\ | ||
a=mid:0\r\n\ | ||
a=recvonly\r\n\ | ||
@@ -699,5 +632,5 @@ a=rtcp-mux\r\n\ | ||
a=fingerprint:sha-256 E7:70:CE:58:6A:CC:77:B0:B4:4B:F2:BC:7E:89:0D:69:E3:90:F3:7A:11:78:B1:5A:CD:E6:41:19:14:EB:56:49\r\n\ | ||
m=video 1 UDP/TLS/RTP/SAVPF 100 116 117 96\r\n\ | ||
m=audio 0 UDP/TLS/RTP/SAVPF 111 0\r\n\ | ||
a=inactive\r\n\ | ||
a=mid:video-65477720\r\n\ | ||
a=mid:1\r\n\ | ||
m=video 1 UDP/TLS/RTP/SAVPF 100\r\n\ | ||
@@ -713,3 +646,3 @@ b=AS:2000\r\n\ | ||
a=setup:active\r\n\ | ||
a=mid:video-774581929\r\n\ | ||
a=mid:2\r\n\ | ||
a=recvonly\r\n\ | ||
@@ -720,3 +653,6 @@ a=rtcp-mux\r\n\ | ||
a=ice-pwd:Ab5LzP5Wn5dBfC6ct6Xhg3\r\n\ | ||
a=fingerprint:sha-256 E7:70:CE:58:6A:CC:77:B0:B4:4B:F2:BC:7E:89:0D:69:E3:90:F3:7A:11:78:B1:5A:CD:E6:41:19:14:EB:56:49\r\n" | ||
a=fingerprint:sha-256 E7:70:CE:58:6A:CC:77:B0:B4:4B:F2:BC:7E:89:0D:69:E3:90:F3:7A:11:78:B1:5A:CD:E6:41:19:14:EB:56:49\r\n\ | ||
m=video 1 UDP/TLS/RTP/SAVPF 100 116 117 96\r\n\ | ||
a=inactive\r\n\ | ||
a=mid:3\r\n" | ||
@@ -945,2 +881,6 @@ /*jshint multistr: true */ | ||
a=ice-options:trickle\r\n\ | ||
a=ssrc:2369716513 cname:agmig8AM0fZtXKl0\r\n\ | ||
a=ssrc:2369716513 msid:5341c16e-abc0-4e8c-90ba-7b2d8b75daf3 8b39a943-fad1-47a0-92a8-3bf63fcb8c09\r\n\ | ||
a=ssrc:2369716513 mslabel:5341c16e-abc0-4e8c-90ba-7b2d8b75daf3\r\n\ | ||
a=ssrc:2369716513 label:8b39a943-fad1-47a0-92a8-3bf63fcb8c09\r\n\ | ||
a=ssrc:624523265 cname:agmig8AM0fZtXKl0\r\n\ | ||
@@ -950,6 +890,2 @@ a=ssrc:624523265 msid:5341c16e-abc0-4e8c-90ba-7b2d8b75daf3 8b39a943-fad1-47a0-92a8-3bf63fcb8c09\r\n\ | ||
a=ssrc:624523265 label:8b39a943-fad1-47a0-92a8-3bf63fcb8c09\r\n\ | ||
a=ssrc:2369716513 cname:agmig8AM0fZtXKl0\r\n\ | ||
a=ssrc:2369716513 msid:5341c16e-abc0-4e8c-90ba-7b2d8b75daf3 8b39a943-fad1-47a0-92a8-3bf63fcb8c09\r\n\ | ||
a=ssrc:2369716513 mslabel:5341c16e-abc0-4e8c-90ba-7b2d8b75daf3\r\n\ | ||
a=ssrc:2369716513 label:8b39a943-fad1-47a0-92a8-3bf63fcb8c09\r\n\ | ||
a=ssrc-group:FID 2369716513 624523265\r\n\ | ||
@@ -1209,2 +1145,3 @@ a=rtcp-mux\r\n\ | ||
QUnit.test('3-way-jitsi', function (assert) { | ||
@@ -1211,0 +1148,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
0
Yes
485857
10
14
4000
204