Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref(SDPDiffer) Convert to ES6 class. #2577

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
248 changes: 87 additions & 161 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');

if (!localSDP || typeof localSDP !== 'string') {
return ssrcMap;
}

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

if (!Array.isArray(session.media)) {
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 @@ -1322,15 +1292,12 @@ const getters = {

// For a jvb connection, transform the SDP to Plan B first.
if (!this.isP2P) {
desc = this.interop.toPlanB(desc);
this.trace('getLocalDescription::postTransform (Plan B)', dumpSDP(desc));

desc = this._injectSsrcGroupForUnifiedSimulcast(desc);
this.trace('getLocalDescription::postTransform (inject ssrc group)', dumpSDP(desc));
}

// 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 @@ -1505,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 @@ -1516,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 @@ -1643,7 +1616,7 @@ TraceablePeerConnection.prototype.getConfiguredVideoCodec = function(localTrack)
* @returns {Array}
*/
TraceablePeerConnection.prototype.getConfiguredVideoCodecs = function(description) {
const currentSdp = description?.sdp ?? this.peerconnection.localDescription?.sdp;
const currentSdp = description?.sdp ?? this.localDescription?.sdp;

if (!currentSdp) {
return [];
Expand Down Expand Up @@ -1746,7 +1719,7 @@ TraceablePeerConnection.prototype.findSenderForTrack = function(track) {
* @returns {void}
*/
TraceablePeerConnection.prototype.processLocalSdpForTransceiverInfo = function(localTracks) {
const localSdp = this.peerconnection.localDescription?.sdp;
const localSdp = this.localDescription?.sdp;

if (!localSdp) {
return;
Expand Down Expand Up @@ -1817,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 @@ -2599,9 +2570,7 @@ TraceablePeerConnection.prototype.createOffer = function(constraints) {
return this._createOfferOrAnswer(true /* offer */, constraints);
};

TraceablePeerConnection.prototype._createOfferOrAnswer = function(
isOffer,
constraints) {
TraceablePeerConnection.prototype._createOfferOrAnswer = function(isOffer, constraints) {
const logName = isOffer ? 'Offer' : 'Answer';

this.trace(`create${logName}`, JSON.stringify(constraints, null, ' '));
Expand Down Expand Up @@ -2631,9 +2600,7 @@ TraceablePeerConnection.prototype._createOfferOrAnswer = function(
dumpSDP(resultSdp));
}

const ssrcMap = this._extractSSRCMap(resultSdp);

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

resolveFn(resultSdp);
} catch (e) {
Expand Down Expand Up @@ -2720,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