import { RtpEncodingParameters } from '../../RtpParameters'; export function getRtpEncodings( { offerMediaObject, track }: { offerMediaObject: any; track: MediaStreamTrack; } ): RtpEncodingParameters[] { // First media SSRC (or the only one). let firstSsrc; const ssrcs = new Set(); for (const line of offerMediaObject.ssrcs || []) { if (line.attribute !== 'msid') continue; const trackId = line.value.split(' ')[1]; if (trackId === track.id) { const ssrc = line.id; ssrcs.add(ssrc); if (!firstSsrc) firstSsrc = ssrc; } } if (ssrcs.size === 0) throw new Error(`a=ssrc line with msid information not found [track.id:${track.id}]`); const ssrcToRtxSsrc = new Map(); // First assume RTX is used. for (const line of offerMediaObject.ssrcGroups || []) { if (line.semantics !== 'FID') continue; let [ ssrc, rtxSsrc ] = line.ssrcs.split(/\s+/); ssrc = Number(ssrc); rtxSsrc = Number(rtxSsrc); if (ssrcs.has(ssrc)) { // Remove both the SSRC and RTX SSRC from the set so later we know that they // are already handled. ssrcs.delete(ssrc); ssrcs.delete(rtxSsrc); // Add to the map. ssrcToRtxSsrc.set(ssrc, rtxSsrc); } } // If the set of SSRCs is not empty it means that RTX is not being used, so take // media SSRCs from there. for (const ssrc of ssrcs) { // Add to the map. ssrcToRtxSsrc.set(ssrc, null); } const encodings: RtpEncodingParameters[] = []; for (const [ ssrc, rtxSsrc ] of ssrcToRtxSsrc) { const encoding: any = { ssrc }; if (rtxSsrc) encoding.rtx = { ssrc: rtxSsrc }; encodings.push(encoding); } return encodings; } /** * Adds multi-ssrc based simulcast into the given SDP media section offer. */ export function addLegacySimulcast( { offerMediaObject, track, numStreams }: { offerMediaObject: any; track: MediaStreamTrack; numStreams: number; } ): void { if (numStreams <= 1) throw new TypeError('numStreams must be greater than 1'); let firstSsrc: any; let firstRtxSsrc: any; let streamId: any; // Get the SSRC. const ssrcMsidLine = (offerMediaObject.ssrcs || []) .find((line: any) => { if (line.attribute !== 'msid') return false; const trackId = line.value.split(' ')[1]; if (trackId === track.id) { firstSsrc = line.id; streamId = line.value.split(' ')[0]; return true; } else { return false; } }); if (!ssrcMsidLine) throw new Error(`a=ssrc line with msid information not found [track.id:${track.id}]`); // Get the SSRC for RTX. (offerMediaObject.ssrcGroups || []) .some((line: any) => { if (line.semantics !== 'FID') return false; const ssrcs = line.ssrcs.split(/\s+/); if (Number(ssrcs[0]) === firstSsrc) { firstRtxSsrc = Number(ssrcs[1]); return true; } else { return false; } }); const ssrcCnameLine = offerMediaObject.ssrcs .find((line: any) => (line.attribute === 'cname' && line.id === firstSsrc)); if (!ssrcCnameLine) throw new Error(`a=ssrc line with cname information not found [track.id:${track.id}]`); const cname = ssrcCnameLine.value; const ssrcs = []; const rtxSsrcs = []; for (let i = 0; i < numStreams; ++i) { ssrcs.push(firstSsrc + i); if (firstRtxSsrc) rtxSsrcs.push(firstRtxSsrc + i); } offerMediaObject.ssrcGroups = offerMediaObject.ssrcGroups || []; offerMediaObject.ssrcs = offerMediaObject.ssrcs || []; offerMediaObject.ssrcGroups.push( { semantics : 'SIM', ssrcs : ssrcs.join(' ') }); for (let i = 0; i < ssrcs.length; ++i) { const ssrc = ssrcs[i]; offerMediaObject.ssrcs.push( { id : ssrc, attribute : 'cname', value : cname }); offerMediaObject.ssrcs.push( { id : ssrc, attribute : 'msid', value : `${streamId} ${track.id}` }); } for (let i = 0; i < rtxSsrcs.length; ++i) { const ssrc = ssrcs[i]; const rtxSsrc = rtxSsrcs[i]; offerMediaObject.ssrcs.push( { id : rtxSsrc, attribute : 'cname', value : cname }); offerMediaObject.ssrcs.push( { id : rtxSsrc, attribute : 'msid', value : `${streamId} ${track.id}` }); offerMediaObject.ssrcGroups.push( { semantics : 'FID', ssrcs : `${ssrc} ${rtxSsrc}` }); } }