UNPKG

mediasoup-client

Version:

mediasoup client side TypeScript library

208 lines (207 loc) 7.08 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.extractRtpCapabilities = extractRtpCapabilities; exports.extractDtlsParameters = extractDtlsParameters; exports.getCname = getCname; exports.applyCodecParameters = applyCodecParameters; exports.addHeaderExtension = addHeaderExtension; const sdpTransform = require("sdp-transform"); /** * This function extracs RTP capabilities from the given SDP. * * BUNDLE is assumed so, as per spec, all media sections in the SDP must share * same ids for codecs and RTP extensions. */ function extractRtpCapabilities({ sdpObject, }) { // Map of RtpCodecParameters indexed by payload type. const codecsMap = new Map(); // Map of RtpHeaderExtensions indexed by preferred id. const headerExtensionMap = new Map(); for (const m of sdpObject.media) { const kind = m.type; switch (kind) { case 'audio': case 'video': { break; } default: { continue; } } // Get codecs. for (const rtp of m.rtp) { const codec = { kind: kind, mimeType: `${kind}/${rtp.codec}`, preferredPayloadType: rtp.payload, clockRate: rtp.rate, channels: rtp.encoding, parameters: {}, rtcpFeedback: [], }; codecsMap.set(codec.preferredPayloadType, codec); } // Get codec parameters. for (const fmtp of m.fmtp ?? []) { const parameters = sdpTransform.parseParams(fmtp.config); const codec = codecsMap.get(fmtp.payload); if (!codec) { continue; } // Specials cases to convert parameter value to string. if (parameters?.hasOwnProperty('profile-level-id')) { parameters['profile-level-id'] = String(parameters['profile-level-id']); } codec.parameters = parameters; } // Get RTCP feedback for each codec. for (const fb of m.rtcpFb ?? []) { const feedback = { type: fb.type, parameter: fb.subtype, }; if (!feedback.parameter) { delete feedback.parameter; } // rtcp-fb payload is not '*', so just apply it to its corresponding // codec. if (fb.payload !== '*') { const codec = codecsMap.get(Number(fb.payload)); if (!codec) { continue; } codec.rtcpFeedback.push(feedback); } // If rtcp-fb payload is '*' it must be applied to all codecs with same // kind (with some exceptions such as RTX codec). else { for (const codec of codecsMap.values()) { if (codec.kind === kind && !/.+\/rtx$/i.test(codec.mimeType)) { codec.rtcpFeedback.push(feedback); } } } } // Get RTP header extensions. for (const ext of m.ext ?? []) { // Ignore encrypted extensions (not yet supported in mediasoup). if (ext['encrypt-uri']) { continue; } const headerExtension = { kind: kind, uri: ext.uri, preferredId: ext.value, }; headerExtensionMap.set(headerExtension.preferredId, headerExtension); } } const rtpCapabilities = { codecs: Array.from(codecsMap.values()), headerExtensions: Array.from(headerExtensionMap.values()), }; return rtpCapabilities; } function extractDtlsParameters({ sdpObject, }) { let setup = sdpObject.setup; let fingerprint = sdpObject.fingerprint; if (!setup || !fingerprint) { const mediaObject = (sdpObject.media ?? []).find((m) => m.port !== 0); if (mediaObject) { setup = setup ?? mediaObject.setup; fingerprint = fingerprint ?? mediaObject.fingerprint; } } if (!setup) { throw new Error('no a=setup found at SDP session or media level'); } else if (!fingerprint) { throw new Error('no a=fingerprint found at SDP session or media level'); } let role; switch (setup) { case 'active': { role = 'client'; break; } case 'passive': { role = 'server'; break; } case 'actpass': { role = 'auto'; break; } } const dtlsParameters = { role, fingerprints: [ { algorithm: fingerprint.type, value: fingerprint.hash, }, ], }; return dtlsParameters; } function getCname({ offerMediaObject, }) { const ssrcCnameLine = (offerMediaObject.ssrcs ?? []).find((line) => line.attribute === 'cname'); if (!ssrcCnameLine) { return ''; } return ssrcCnameLine.value; } /** * Apply codec parameters in the given SDP m= section answer based on the * given RTP parameters of an offer. */ function applyCodecParameters({ offerRtpParameters, answerMediaObject, }) { for (const codec of offerRtpParameters.codecs) { const mimeType = codec.mimeType.toLowerCase(); // Avoid parsing codec parameters for unhandled codecs. if (mimeType !== 'audio/opus') { continue; } const rtp = (answerMediaObject.rtp ?? []).find((r) => r.payload === codec.payloadType); if (!rtp) { continue; } // Just in case. answerMediaObject.fmtp = answerMediaObject.fmtp ?? []; let fmtp = answerMediaObject.fmtp.find((f) => f.payload === codec.payloadType); if (!fmtp) { fmtp = { payload: codec.payloadType, config: '' }; answerMediaObject.fmtp.push(fmtp); } const parameters = sdpTransform.parseParams(fmtp.config); switch (mimeType) { case 'audio/opus': { const spropStereo = codec.parameters?.['sprop-stereo']; if (spropStereo !== undefined) { parameters['stereo'] = Number(spropStereo) ? 1 : 0; } break; } } // Write the codec fmtp.config back. fmtp.config = ''; for (const key of Object.keys(parameters)) { if (fmtp.config) { fmtp.config += ';'; } fmtp.config += `${key}=${parameters[key]}`; } } } /** * Add header extension in the given SDP m= section offer. */ function addHeaderExtension({ offerMediaObject, headerExtensionUri, headerExtensionId, }) { if (!offerMediaObject.ext) { offerMediaObject.ext = []; } offerMediaObject.ext.push({ uri: headerExtensionUri, value: headerExtensionId, }); }