/* global __filename */ import { getLogger } from 'jitsi-meet-logger'; import SDPUtil from './SDPUtil'; import { parseSecondarySSRC, SdpTransformWrap } from './SdpTransformUtil'; const logger = getLogger(__filename); /** * Begin helper functions */ /** * Updates or inserts the appropriate rtx information for primarySsrc with * the given rtxSsrc. If no rtx ssrc for primarySsrc currently exists, it will * add the appropriate ssrc and ssrc group lines. If primarySsrc already has * an rtx ssrc, the appropriate ssrc and group lines will be updated * @param {MLineWrap} mLine * @param {object} primarySsrcInfo the info (ssrc, msid & cname) for the * primary ssrc * @param {number} rtxSsrc the rtx ssrc to associate with the primary ssrc */ function updateAssociatedRtxStream(mLine, primarySsrcInfo, rtxSsrc) { logger.debug( `Updating mline to associate ${rtxSsrc}` + `rtx ssrc with primary stream, ${primarySsrcInfo.id}`); const primarySsrc = primarySsrcInfo.id; const primarySsrcMsid = primarySsrcInfo.msid; const primarySsrcCname = primarySsrcInfo.cname; const previousRtxSSRC = mLine.getRtxSSRC(primarySsrc); if (previousRtxSSRC === rtxSsrc) { logger.debug(`${rtxSsrc} was already associated with ${primarySsrc}`); return; } if (previousRtxSSRC) { logger.debug( `${primarySsrc} was previously associated with rtx` + `${previousRtxSSRC}, removing all references to it`); // Stream already had an rtx ssrc that is different than the one given, // remove all trace of the old one mLine.removeSSRC(previousRtxSSRC); logger.debug(`groups before filtering for ${previousRtxSSRC}`); logger.debug(mLine.dumpSSRCGroups()); mLine.removeGroupsWithSSRC(previousRtxSSRC); } mLine.addSSRCAttribute({ id: rtxSsrc, attribute: 'cname', value: primarySsrcCname }); mLine.addSSRCAttribute({ id: rtxSsrc, attribute: 'msid', value: primarySsrcMsid }); mLine.addSSRCGroup({ semantics: 'FID', ssrcs: `${primarySsrc} ${rtxSsrc}` }); } /** * End helper functions */ /** * Adds any missing RTX streams for video streams * and makes sure that they remain consistent */ export default class RtxModifier { /** * Constructor */ constructor() { /** * Map of video ssrc to corresponding RTX * ssrc */ this.correspondingRtxSsrcs = new Map(); } /** * Clear the cached map of primary video ssrcs to * their corresponding rtx ssrcs so that they will * not be used for the next call to modifyRtxSsrcs */ clearSsrcCache() { this.correspondingRtxSsrcs.clear(); } /** * Explicitly set the primary video ssrc -> rtx ssrc * mapping to be used in modifyRtxSsrcs * @param {Map} ssrcMapping a mapping of primary video * ssrcs to their corresponding rtx ssrcs */ setSsrcCache(ssrcMapping) { logger.debug('Setting ssrc cache to ', ssrcMapping); this.correspondingRtxSsrcs = ssrcMapping; } /** * Adds RTX ssrcs for any video ssrcs that don't * already have them. If the video ssrc has been * seen before, and already had an RTX ssrc generated, * the same RTX ssrc will be used again. * @param {string} sdpStr sdp in raw string format */ modifyRtxSsrcs(sdpStr) { const sdpTransformer = new SdpTransformWrap(sdpStr); const videoMLine = sdpTransformer.selectMedia('video'); if (!videoMLine) { logger.debug(`No 'video' media found in the sdp: ${sdpStr}`); return sdpStr; } return this.modifyRtxSsrcs2(videoMLine) ? sdpTransformer.toRawSDP() : sdpStr; } /** * Does the same thing as {@link modifyRtxSsrcs}, but takes the * {@link MLineWrap} instance wrapping video media as an argument. * @param {MLineWrap} videoMLine * @return {boolean} true if the SDP wrapped by * {@link SdpTransformWrap} has been modified or false otherwise. */ modifyRtxSsrcs2(videoMLine) { if (videoMLine.direction === 'recvonly') { logger.debug('RtxModifier doing nothing, video m line is recvonly'); return false; } if (videoMLine.getSSRCCount() < 1) { logger.debug('RtxModifier doing nothing, no video ssrcs present'); return false; } logger.debug('Current ssrc mapping: ', this.correspondingRtxSsrcs); const primaryVideoSsrcs = videoMLine.getPrimaryVideoSSRCs(); logger.debug('Parsed primary video ssrcs ', primaryVideoSsrcs, ' making sure all have rtx streams'); for (const ssrc of primaryVideoSsrcs) { const msid = videoMLine.getSSRCAttrValue(ssrc, 'msid'); const cname = videoMLine.getSSRCAttrValue(ssrc, 'cname'); let correspondingRtxSsrc = this.correspondingRtxSsrcs.get(ssrc); if (correspondingRtxSsrc) { logger.debug( 'Already have an associated rtx ssrc for' + `video ssrc ${ssrc}: ${correspondingRtxSsrc}`); } else { logger.debug( `No previously associated rtx ssrc for video ssrc ${ssrc}`); // If there's one in the sdp already for it, we'll just set // that as the corresponding one const previousAssociatedRtxStream = videoMLine.getRtxSSRC(ssrc); if (previousAssociatedRtxStream) { logger.debug( `Rtx stream ${previousAssociatedRtxStream} ` + 'already existed in the sdp as an rtx stream for ' + `${ssrc}`); correspondingRtxSsrc = previousAssociatedRtxStream; } else { correspondingRtxSsrc = SDPUtil.generateSsrc(); logger.debug(`Generated rtx ssrc ${correspondingRtxSsrc} ` + `for ssrc ${ssrc}`); } logger.debug(`Caching rtx ssrc ${correspondingRtxSsrc} ` + `for video ssrc ${ssrc}`); this.correspondingRtxSsrcs.set(ssrc, correspondingRtxSsrc); } updateAssociatedRtxStream( videoMLine, { id: ssrc, cname, msid }, correspondingRtxSsrc); } // FIXME we're not looking into much details whether the SDP has been // modified or not once the precondition requirements are met. return true; } /** * Strip all rtx streams from the given sdp * @param {string} sdpStr sdp in raw string format * @returns {string} sdp string with all rtx streams stripped */ stripRtx(sdpStr) { const sdpTransformer = new SdpTransformWrap(sdpStr); const videoMLine = sdpTransformer.selectMedia('video'); if (!videoMLine) { logger.debug(`No 'video' media found in the sdp: ${sdpStr}`); return sdpStr; } if (videoMLine.direction === 'recvonly') { logger.debug('RtxModifier doing nothing, video m line is recvonly'); return sdpStr; } if (videoMLine.getSSRCCount() < 1) { logger.debug('RtxModifier doing nothing, no video ssrcs present'); return sdpStr; } if (!videoMLine.containsAnySSRCGroups()) { logger.debug('RtxModifier doing nothing, ' + 'no video ssrcGroups present'); return sdpStr; } const fidGroups = videoMLine.findGroups('FID'); // Remove the fid groups from the mline videoMLine.removeGroupsBySemantics('FID'); // Get the rtx ssrcs and remove them from the mline for (const fidGroup of fidGroups) { const rtxSsrc = parseSecondarySSRC(fidGroup); videoMLine.removeSSRC(rtxSsrc); } return sdpTransformer.toRawSDP(); } }