mediasoup-client
Version:
mediasoup client side TypeScript library
208 lines (207 loc) • 7.08 kB
JavaScript
;
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,
});
}