UNPKG

@twilio/voice-sdk

Version:
159 lines 14.5 kB
// @ts-nocheck import * as util from '../util'; const ptToFixedBitrateAudioCodecName = { 0: 'PCMU', 8: 'PCMA', }; const defaultOpusId = 111; const BITRATE_MAX = 510000; const BITRATE_MIN = 6000; function getPreferredCodecInfo(sdp) { const [, codecId, codecName] = /a=rtpmap:(\d+) (\S+)/m.exec(sdp) || [null, '', '']; const regex = new RegExp(`a=fmtp:${codecId} (\\S+)`, 'm'); const [, codecParams] = regex.exec(sdp) || [null, '']; return { codecName, codecParams }; } function setIceAggressiveNomination(sdp) { // This only works on Chrome. We don't want any side effects on other browsers // https://bugs.chromium.org/p/chromium/issues/detail?id=1024096 // https://issues.corp.twilio.com/browse/CLIENT-6911 if (!util.isChrome(window, window.navigator)) { return sdp; } return sdp.split('\n') .filter(line => line.indexOf('a=ice-lite') === -1) .join('\n'); } function setMaxAverageBitrate(sdp, maxAverageBitrate) { if (typeof maxAverageBitrate !== 'number' || maxAverageBitrate < BITRATE_MIN || maxAverageBitrate > BITRATE_MAX) { return sdp; } const matches = /a=rtpmap:(\d+) opus/m.exec(sdp); const opusId = matches && matches.length ? matches[1] : defaultOpusId; const regex = new RegExp(`a=fmtp:${opusId}`); const lines = sdp.split('\n').map(line => regex.test(line) ? line + `;maxaveragebitrate=${maxAverageBitrate}` : line); return lines.join('\n'); } /** * Return a new SDP string with the re-ordered codec preferences. * @param {string} sdp * @param {Array<AudioCodec>} preferredCodecs - If empty, the existing order * of audio codecs is preserved * @returns {string} Updated SDP string */ function setCodecPreferences(sdp, preferredCodecs) { const mediaSections = getMediaSections(sdp); const session = sdp.split('\r\nm=')[0]; return [session].concat(mediaSections.map(section => { // Codec preferences should not be applied to m=application sections. if (!/^m=(audio|video)/.test(section)) { return section; } const kind = section.match(/^m=(audio|video)/)[1]; const codecMap = createCodecMapForMediaSection(section); const payloadTypes = getReorderedPayloadTypes(codecMap, preferredCodecs); const newSection = setPayloadTypesInMediaSection(payloadTypes, section); const pcmaPayloadTypes = codecMap.get('pcma') || []; const pcmuPayloadTypes = codecMap.get('pcmu') || []; const fixedBitratePayloadTypes = kind === 'audio' ? new Set(pcmaPayloadTypes.concat(pcmuPayloadTypes)) : new Set(); return fixedBitratePayloadTypes.has(payloadTypes[0]) ? newSection.replace(/\r\nb=(AS|TIAS):([0-9]+)/g, '') : newSection; })).join('\r\n'); } /** * Get the m= sections of a particular kind and direction from an sdp. * @param {string} sdp - SDP string * @param {string} [kind] - Pattern for matching kind * @param {string} [direction] - Pattern for matching direction * @returns {Array<string>} mediaSections */ function getMediaSections(sdp, kind, direction) { return sdp.replace(/\r\n\r\n$/, '\r\n').split('\r\nm=').slice(1).map(mediaSection => `m=${mediaSection}`).filter(mediaSection => { const kindPattern = new RegExp(`m=${kind || '.*'}`, 'gm'); const directionPattern = new RegExp(`a=${direction || '.*'}`, 'gm'); return kindPattern.test(mediaSection) && directionPattern.test(mediaSection); }); } /** * Create a Codec Map for the given m= section. * @param {string} section - The given m= section * @returns {Map<Codec, Array<PT>>} */ function createCodecMapForMediaSection(section) { return Array.from(createPtToCodecName(section)).reduce((codecMap, pair) => { const pt = pair[0]; const codecName = pair[1]; const pts = codecMap.get(codecName) || []; return codecMap.set(codecName, pts.concat(pt)); }, new Map()); } /** * Create the reordered Codec Payload Types based on the preferred Codec Names. * @param {Map<Codec, Array<PT>>} codecMap - Codec Map * @param {Array<Codec>} preferredCodecs - Preferred Codec Names * @returns {Array<PT>} Reordered Payload Types */ function getReorderedPayloadTypes(codecMap, preferredCodecs) { preferredCodecs = preferredCodecs.map(codecName => codecName.toLowerCase()); const preferredPayloadTypes = util.flatMap(preferredCodecs, codecName => codecMap.get(codecName) || []); const remainingCodecs = util.difference(Array.from(codecMap.keys()), preferredCodecs); const remainingPayloadTypes = util.flatMap(remainingCodecs, codecName => codecMap.get(codecName)); return preferredPayloadTypes.concat(remainingPayloadTypes); } /** * Set the given Codec Payload Types in the first line of the given m= section. * @param {Array<PT>} payloadTypes - Payload Types * @param {string} section - Given m= section * @returns {string} - Updated m= section */ function setPayloadTypesInMediaSection(payloadTypes, section) { const lines = section.split('\r\n'); let mLine = lines[0]; const otherLines = lines.slice(1); mLine = mLine.replace(/([0-9]+\s?)+$/, payloadTypes.join(' ')); return [mLine].concat(otherLines).join('\r\n'); } /** * Create a Map from PTs to codec names for the given m= section. * @param {string} mediaSection - The given m= section. * @returns {Map<PT, Codec>} ptToCodecName */ function createPtToCodecName(mediaSection) { return getPayloadTypesInMediaSection(mediaSection).reduce((ptToCodecName, pt) => { const rtpmapPattern = new RegExp(`a=rtpmap:${pt} ([^/]+)`); const matches = mediaSection.match(rtpmapPattern); const codecName = matches ? matches[1].toLowerCase() : ptToFixedBitrateAudioCodecName[pt] ? ptToFixedBitrateAudioCodecName[pt].toLowerCase() : ''; return ptToCodecName.set(pt, codecName); }, new Map()); } /** * Get the Codec Payload Types present in the first line of the given m= section * @param {string} section - The m= section * @returns {Array<PT>} Payload Types */ function getPayloadTypesInMediaSection(section) { const mLine = section.split('\r\n')[0]; // In "m=<kind> <port> <proto> <payload_type_1> <payload_type_2> ... <payload_type_n>", // the regex matches <port> and the PayloadTypes. const matches = mLine.match(/([0-9]+)/g); // This should not happen, but in case there are no PayloadTypes in // the m= line, return an empty array. if (!matches) { return []; } // Since only the PayloadTypes are needed, we discard the <port>. return matches.slice(1).map(match => parseInt(match, 10)); } export { getPreferredCodecInfo, setCodecPreferences, setIceAggressiveNomination, setMaxAverageBitrate, }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2RwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vbGliL3R3aWxpby9ydGMvc2RwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWM7QUFDZCxPQUFPLEtBQUssSUFBSSxNQUFNLFNBQVMsQ0FBQztBQUVoQyxNQUFNLDhCQUE4QixHQUFHO0lBQ3JDLENBQUMsRUFBRSxNQUFNO0lBQ1QsQ0FBQyxFQUFFLE1BQU07Q0FDVixDQUFDO0FBRUYsTUFBTSxhQUFhLEdBQUcsR0FBRyxDQUFDO0FBQzFCLE1BQU0sV0FBVyxHQUFHLE1BQU0sQ0FBQztBQUMzQixNQUFNLFdBQVcsR0FBRyxJQUFJLENBQUM7QUFFekIsU0FBUyxxQkFBcUIsQ0FBQyxHQUFHO0lBQ2hDLE1BQU0sQ0FBQyxFQUFFLE9BQU8sRUFBRSxTQUFTLENBQUMsR0FBRyx1QkFBdUIsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsRUFBRSxFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBQ25GLE1BQU0sS0FBSyxHQUFHLElBQUksTUFBTSxDQUFDLFVBQVUsT0FBTyxTQUFTLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDMUQsTUFBTSxDQUFDLEVBQUUsV0FBVyxDQUFDLEdBQUcsS0FBSyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxFQUFFLENBQUMsQ0FBQztJQUN0RCxPQUFPLEVBQUUsU0FBUyxFQUFFLFdBQVcsRUFBRSxDQUFDO0FBQ3BDLENBQUM7QUFFRCxTQUFTLDBCQUEwQixDQUFDLEdBQUc7SUFDckMsOEVBQThFO0lBQzlFLGdFQUFnRTtJQUNoRSxvREFBb0Q7SUFDcEQsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxFQUFFLE1BQU0sQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDO1FBQzdDLE9BQU8sR0FBRyxDQUFDO0lBQ2IsQ0FBQztJQUVELE9BQU8sR0FBRyxDQUFDLEtBQUssQ0FB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