Skip to content

Commit

Permalink
ref(SDP) Always generate the MSID for signaling it to Jicofo.
Browse files Browse the repository at this point in the history
  • Loading branch information
jallamsetty1 committed Oct 11, 2024
1 parent bc79fc6 commit 6466202
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 326 deletions.
13 changes: 10 additions & 3 deletions modules/RTC/TPCUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -360,12 +360,14 @@ export class TPCUtils {

/**
* Adds {@link JitsiLocalTrack} to the WebRTC peerconnection for the first time.
*
* @param {JitsiLocalTrack} track - track to be added to the peerconnection.
* @param {boolean} isInitiator - boolean that indicates if the endpoint is offerer in a p2p connection.
* @returns {void}
* @returns {RTCRtpTransceiver} - the transceiver that the track was added to.
*/
addTrack(localTrack, isInitiator) {
const track = localTrack.getTrack();
let transceiver;

if (isInitiator) {
const streams = [];
Expand All @@ -385,13 +387,18 @@ export class TPCUtils {
if (!browser.isFirefox()) {
transceiverInit.sendEncodings = this._getStreamEncodings(localTrack);
}
this.pc.peerconnection.addTransceiver(track, transceiverInit);
transceiver = this.pc.peerconnection.addTransceiver(track, transceiverInit);
} else {
// Use pc.addTrack() for responder case so that we can re-use the m-lines that were created
// when setRemoteDescription was called. pc.addTrack() automatically attaches to any existing
// unused "recv-only" transceiver.
this.pc.peerconnection.addTrack(track);
const sender = this.pc.peerconnection.addTrack(track);

// Find the corresponding transceiver that the track was attached to.
transceiver = this.pc.peerconnection.getTransceivers().find(t => t.sender === sender);
}

return transceiver;
}

/**
Expand Down
237 changes: 84 additions & 153 deletions modules/RTC/TraceablePeerConnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,13 @@ export default function TraceablePeerConnection(
*/
this._localTrackTransceiverMids = new Map();

/**
* Holds the SSRC map for the local tracks.
*
* @type {Map<string, TPCSSRCInfo>}
*/
this._ssrcMap = null;

// override as desired
this.trace = (what, info) => {
logger.trace(what, info);
Expand Down Expand Up @@ -1123,117 +1130,80 @@ TraceablePeerConnection.prototype._removeRemoteTrack = function(toBeRemoved) {
};

/**
* Returns a map with keys msid/mediaType and <tt>TrackSSRCInfo</tt> values.
* @param {RTCSessionDescription} desc the local description.
* @return {Map<string,TrackSSRCInfo>}
* Processes the local SDP and creates an SSRC map for every local track.
*
* @param {string} localSDP - SDP from the local description.
* @returns {void}
*/
TraceablePeerConnection.prototype._extractSSRCMap = function(desc) {
/**
* Track SSRC infos mapped by stream ID (msid) or mediaType (unified-plan)
* @type {Map<string,TrackSSRCInfo>}
*/
TraceablePeerConnection.prototype._processAndExtractSourceInfo = function(localSDP) {
const ssrcMap = new Map();

/**
* Groups mapped by primary SSRC number
* @type {Map<number,Array<SSRCGroupInfo>>}
*/
const groupsMap = new Map();

if (typeof desc !== 'object' || desc === null
|| typeof desc.sdp !== 'string') {
logger.warn('An empty description was passed as an argument');

return ssrcMap;
}

const session = transform.parse(desc.sdp);

if (!Array.isArray(session.media)) {
if (!localSDP || typeof localSDP !== 'string') {
return ssrcMap;
}

let media = session.media;

media = media.filter(mline => mline.direction === MediaDirection.SENDONLY
const session = transform.parse(localSDP);
const media = session.media.filter(mline => mline.direction === MediaDirection.SENDONLY
|| mline.direction === MediaDirection.SENDRECV);

let index = 0;

for (const mLine of media) {
if (!Array.isArray(mLine.ssrcs)) {
continue; // eslint-disable-line no-continue
}
if (!Array.isArray(media)) {
return;
}

if (Array.isArray(mLine.ssrcGroups)) {
for (const group of mLine.ssrcGroups) {
if (typeof group.semantics !== 'undefined' && typeof group.ssrcs !== 'undefined') {
// Parse SSRCs and store as numbers
const groupSSRCs = group.ssrcs.split(' ').map(ssrcStr => parseInt(ssrcStr, 10));
const primarySSRC = groupSSRCs[0];
for (const localTrack of this.localTracks.values()) {
const sourceName = localTrack.getSourceName();
const trackIndex = getSourceIndexFromSourceName(sourceName);
const mediaType = localTrack.getType();
const mLines = media.filter(m => m.type === mediaType);
const ssrcGroups = mLines[trackIndex].ssrcGroups;
let ssrcs = mLines[trackIndex].ssrcs;

if (ssrcs?.length) {
// Filter the ssrcs with 'cname' attribute.
ssrcs = ssrcs.filter(s => s.attribute === 'cname');

const msid = `${this.rtc.getLocalEndpointId()}-${mediaType}-${trackIndex}`;
const ssrcInfo = {
ssrcs: [],
groups: [],
msid
};

// Note that group.semantics is already present
group.ssrcs = groupSSRCs;
ssrcs.forEach(ssrc => ssrcInfo.ssrcs.push(ssrc.id));

// eslint-disable-next-line max-depth
if (!groupsMap.has(primarySSRC)) {
groupsMap.set(primarySSRC, []);
}
groupsMap.get(primarySSRC).push(group);
if (Array.isArray(ssrcGroups)) {
for (const group of ssrcGroups) {
group.ssrcs = group.ssrcs.split(' ').map(ssrcStr => parseInt(ssrcStr, 10));
ssrcInfo.groups.push(group);
}
}

const simGroup = mLine.ssrcGroups.find(group => group.semantics === 'SIM');
const simGroup = ssrcGroups.find(group => group.semantics === 'SIM');

// Add a SIM group if its missing in the description (happens on Firefox).
if (!simGroup) {
const groupSsrcs = mLine.ssrcGroups.map(group => group.ssrcs[0]);
// Add a SIM group if its missing in the description (happens on Firefox).
if (this.isSpatialScalabilityOn() && !simGroup) {
const groupSsrcs = ssrcGroups.map(group => group.ssrcs[0]);

groupsMap.get(groupSsrcs[0]).push({
semantics: 'SIM',
ssrcs: groupSsrcs
});
ssrcInfo.groups.push({
semantics: 'SIM',
ssrcs: groupSsrcs
});
}
}
}

let ssrcs = mLine.ssrcs;

// Filter the ssrcs with 'cname' attribute.
ssrcs = ssrcs.filter(s => s.attribute === 'cname');

for (const ssrc of ssrcs) {
// Use the mediaType as key for the source map for unified plan clients since msids are not part of
// the standard and the unified plan SDPs do not have a proper msid attribute for the sources.
// Also the ssrcs for sources do not change for Unified plan clients since RTCRtpSender#replaceTrack is
// used for switching the tracks so it is safe to use the mediaType as the key for the TrackSSRCInfo map.
const key = `${mLine.type}-${index}`;
const ssrcNumber = ssrc.id;
let ssrcInfo = ssrcMap.get(key);

if (!ssrcInfo) {
ssrcInfo = {
ssrcs: [],
groups: [],
msid: key
};
ssrcMap.set(key, ssrcInfo);
}
ssrcInfo.ssrcs.push(ssrcNumber);
ssrcMap.set(sourceName, ssrcInfo);

if (groupsMap.has(ssrcNumber)) {
const ssrcGroups = groupsMap.get(ssrcNumber);
const oldSsrcInfo = this.localSSRCs.get(localTrack.rtcId);
const oldSsrc = this._extractPrimarySSRC(oldSsrcInfo);
const newSsrc = this._extractPrimarySSRC(ssrcInfo);

for (const group of ssrcGroups) {
ssrcInfo.groups.push(group);
}
if (oldSsrc !== newSsrc) {
oldSsrc && logger.error(`${this} Overwriting SSRC for track=${localTrack}] with ssrc=${newSsrc}`);
this.localSSRCs.set(localTrack.rtcId, ssrcInfo);
localTrack.setSsrc(newSsrc);
this.eventEmitter.emit(RTCEvents.LOCAL_TRACK_SSRC_UPDATED, localTrack, newSsrc);
}
}

// Currently multi-stream is supported for video only.
mLine.type === MediaType.VIDEO && index++;
}

return ssrcMap;
this._ssrcMap = ssrcMap;
};

/**
Expand Down Expand Up @@ -1327,7 +1297,7 @@ const getters = {
}

// See the method's doc for more info about this transformation.
desc = this.localSdpMunger.transformStreamIdentifiers(desc);
desc = this.localSdpMunger.transformStreamIdentifiers(desc, this._ssrcMap);

return desc;
},
Expand Down Expand Up @@ -1502,6 +1472,7 @@ TraceablePeerConnection.prototype._updateAv1DdHeaders = function(description) {
*/
TraceablePeerConnection.prototype.addTrack = function(track, isInitiator = false) {
const rtcId = track.rtcId;
let transceiver;

logger.info(`${this} adding ${track}`);
if (this.localTracks.has(rtcId)) {
Expand All @@ -1513,7 +1484,12 @@ TraceablePeerConnection.prototype.addTrack = function(track, isInitiator = false
const webrtcStream = track.getOriginalStream();

try {
this.tpcUtils.addTrack(track, isInitiator);
transceiver = this.tpcUtils.addTrack(track, isInitiator);

if (transceiver?.mid) {
this._localTrackTransceiverMids.set(track.rtcId, transceiver.mid.toString());
}

if (track) {
if (track.isAudioTrack()) {
this._hasHadAudioTrack = true;
Expand Down Expand Up @@ -1814,23 +1790,21 @@ TraceablePeerConnection.prototype.replaceTrack = function(oldTrack, newTrack) {
}
}

if (transceiver) {
// In the scenario where we remove the oldTrack (oldTrack is not null and newTrack is null) on FF
// if we change the direction to RECVONLY, create answer will generate SDP with only 1 receive
// only ssrc instead of keeping all 6 ssrcs that we currently have. Stopping the screen sharing
// and then starting it again will trigger 2 rounds of source-remove and source-add replacing
// the 6 ssrcs for the screen sharing with 1 receive only ssrc and then removing the receive
// only ssrc and adding the same 6 ssrcs. On the remote participant's side the same ssrcs will
// be reused on a new m-line and if the remote participant is FF due to
// https://bugzilla.mozilla.org/show_bug.cgi?id=1768729 the video stream won't be rendered.
// That's why we need keep the direction to SENDRECV for FF.
//
// NOTE: If we return back to the approach of not removing the track for FF and instead using the
// enabled property for mute or stopping screensharing we may need to change the direction to
// RECVONLY if FF still sends the media even though the enabled flag is set to false.
transceiver.direction
= newTrack || browser.isFirefox() ? MediaDirection.SENDRECV : MediaDirection.RECVONLY;
}
// In the scenario where we remove the oldTrack (oldTrack is not null and newTrack is null) on FF
// if we change the direction to RECVONLY, create answer will generate SDP with only 1 receive
// only ssrc instead of keeping all 6 ssrcs that we currently have. Stopping the screen sharing
// and then starting it again will trigger 2 rounds of source-remove and source-add replacing
// the 6 ssrcs for the screen sharing with 1 receive only ssrc and then removing the receive
// only ssrc and adding the same 6 ssrcs. On the remote participant's side the same ssrcs will
// be reused on a new m-line and if the remote participant is FF due to
// https://bugzilla.mozilla.org/show_bug.cgi?id=1768729 the video stream won't be rendered.
// That's why we need keep the direction to SENDRECV for FF.
//
// NOTE: If we return back to the approach of not removing the track for FF and instead using the
// enabled property for mute or stopping screensharing we may need to change the direction to
// RECVONLY if FF still sends the media even though the enabled flag is set to false.
transceiver.direction
= newTrack || browser.isFirefox() ? MediaDirection.SENDRECV : MediaDirection.RECVONLY;

// Avoid re-configuring the encodings on Chromium/Safari, this is needed only on Firefox.
const configureEncodingsPromise
Expand Down Expand Up @@ -2626,9 +2600,7 @@ TraceablePeerConnection.prototype._createOfferOrAnswer = function(isOffer, const
dumpSDP(resultSdp));
}

const ssrcMap = this._extractSSRCMap(resultSdp);

this._processLocalSSRCsMap(ssrcMap);
this._processAndExtractSourceInfo(resultSdp.sdp);

resolveFn(resultSdp);
} catch (e) {
Expand Down Expand Up @@ -2715,47 +2687,6 @@ TraceablePeerConnection.prototype._extractPrimarySSRC = function(ssrcObj) {
return null;
};

/**
* Goes over the SSRC map extracted from the latest local description and tries
* to match them with the local tracks (by MSID). Will update the values
* currently stored in the {@link TraceablePeerConnection.localSSRCs} map.
* @param {Map<string,TrackSSRCInfo>} ssrcMap
* @private
*/
TraceablePeerConnection.prototype._processLocalSSRCsMap = function(ssrcMap) {
for (const track of this.localTracks.values()) {
const sourceName = track.getSourceName();
const sourceIndex = getSourceIndexFromSourceName(sourceName);
const sourceIdentifier = `${track.getType()}-${sourceIndex}`;

if (ssrcMap.has(sourceIdentifier)) {
const newSSRC = ssrcMap.get(sourceIdentifier);

if (!newSSRC) {
logger.error(`${this} No SSRC found for stream=${sourceIdentifier}`);

return;
}
const oldSSRC = this.localSSRCs.get(track.rtcId);
const newSSRCNum = this._extractPrimarySSRC(newSSRC);
const oldSSRCNum = this._extractPrimarySSRC(oldSSRC);

// eslint-disable-next-line no-negated-condition
if (newSSRCNum !== oldSSRCNum) {
oldSSRCNum && logger.error(`${this} Overwriting SSRC for track=${track}] with ssrc=${newSSRC}`);
this.localSSRCs.set(track.rtcId, newSSRC);
track.setSsrc(newSSRCNum);
this.eventEmitter.emit(RTCEvents.LOCAL_TRACK_SSRC_UPDATED, track, newSSRCNum);
}
} else if (!track.isVideoTrack() && !track.isMuted()) {
// It is normal to find no SSRCs for a muted video track in
// the local SDP as the recv-only SSRC is no longer munged in.
// So log the warning only if it's not a muted video track.
logger.warn(`${this} No SSRCs found in the local SDP for track=${track}, stream=${sourceIdentifier}`);
}
}
};

/**
* Track the SSRCs seen so far.
* @param {number} ssrc - SSRC.
Expand Down
Loading

0 comments on commit 6466202

Please sign in to comment.