141 lines
4.8 KiB
TypeScript
141 lines
4.8 KiB
TypeScript
import { toTelegramSource } from './utils';
|
|
import type { JoinGroupCallPayload, SsrcGroup } from './types';
|
|
|
|
export default (sessionDescription: RTCSessionDescriptionInit, isP2p = false): JoinGroupCallPayload => {
|
|
if (!sessionDescription || !sessionDescription.sdp) {
|
|
throw Error('Failed parsing SDP: session description is null');
|
|
}
|
|
|
|
const sections = sessionDescription
|
|
.sdp
|
|
.split('\r\nm=')
|
|
.map((s, i) => (i === 0 ? s : `m=${s}`))
|
|
.reduce((acc: Record<string, string[]>, el) => {
|
|
const name = el.match(/^m=(.+?)\s/)?.[1] || 'header';
|
|
acc[acc.hasOwnProperty(name) && name === 'video' ? 'screencast' : name] = el.split('\r\n').filter(Boolean);
|
|
return acc;
|
|
}, {});
|
|
|
|
const lookup = (prefix: string, sectionName?: string) => {
|
|
if (!sectionName) {
|
|
return Object.values(sections).map((section) => {
|
|
return section.find((line) => line.startsWith(prefix))?.substr(prefix.length);
|
|
}).filter(Boolean)[0];
|
|
} else {
|
|
return sections[sectionName]?.find((line) => line.startsWith(prefix))?.substr(prefix.length);
|
|
}
|
|
};
|
|
|
|
const parseExtmaps = (sectionName: string) => {
|
|
return sections[sectionName].filter((l) => l.startsWith('a=extmap')).map((l) => {
|
|
const [, id, uri] = l.match(/extmap:(\d+)(?:\/.+)?\s(.+)/)!;
|
|
return { id: Number(id), uri };
|
|
});
|
|
};
|
|
|
|
const parsePayloadTypes = (sectionName: string) => {
|
|
const payloads = sections[sectionName].filter((l) => l.startsWith('a=rtpmap')).map((l) => {
|
|
const [, id, data] = l.match(/:(\d+)\s(.+)/) || [];
|
|
const [name, clockrate, channels] = data.split('/');
|
|
return {
|
|
id: Number(id), name, clockrate: Number(clockrate), ...(channels && { channels: Number(channels) }),
|
|
};
|
|
});
|
|
|
|
const fbParams = sections[sectionName].filter((l) => l.startsWith('a=rtcp-fb')).map((l) => {
|
|
const [, id, data] = l.match(/:(\d+)\s(.+)/) || [];
|
|
const [type, subtype] = data.split(' ');
|
|
return { id: Number(id), type, subtype: subtype || '' };
|
|
});
|
|
|
|
const parameters = sections[sectionName].filter((l) => l.startsWith('a=fmtp')).map((l) => {
|
|
const [, id, data] = l.match(/:(\d+)\s(.+)/) || [];
|
|
const d = data?.split(';').reduce((acc: Record<string, string>, q) => {
|
|
const [name, value] = q.split('=');
|
|
acc[name] = value;
|
|
return acc;
|
|
}, {});
|
|
if (!d || Object.values(d).some((z) => !z)) return undefined;
|
|
return { id: Number(id), data: d };
|
|
}).filter(Boolean);
|
|
|
|
return payloads.map((payload) => {
|
|
const p = parameters.filter((l) => l!.id === payload.id).map((q) => q!.data).reduce((acc, el) => {
|
|
return Object.assign(acc, el);
|
|
}, {});
|
|
const f = fbParams.filter((l) => l.id === payload.id).map((l) => {
|
|
return {
|
|
type: l.type,
|
|
subtype: l.subtype,
|
|
};
|
|
});
|
|
|
|
return {
|
|
...payload,
|
|
...(Object.keys(p).length > 0 && { parameters: p }),
|
|
...(f.length > 0 && { feedbackTypes: f }),
|
|
};
|
|
});
|
|
};
|
|
|
|
const rawSource = lookup('a=ssrc:', 'audio');
|
|
const sourceAudio = rawSource && Number(rawSource.split(' ')[0]);
|
|
|
|
// TODO multiple source groups
|
|
const rawSourceVideo = lookup('a=ssrc-group:', 'video')?.split(' ') || undefined;
|
|
const rawSourceScreencast = lookup('a=ssrc-group:', 'screencast')?.split(' ') || undefined;
|
|
|
|
if (!rawSourceVideo) {
|
|
throw Error('Failed parsing SDP: no video ssrc');
|
|
}
|
|
|
|
const [hash, fingerprint] = lookup('a=fingerprint:')?.split(' ') || [];
|
|
|
|
const setup = lookup('a=setup:');
|
|
if (!hash || !fingerprint) {
|
|
throw Error('Failed parsing SDP: no fingerprint');
|
|
}
|
|
|
|
console.log(sections);
|
|
|
|
const ufrag = lookup('a=ice-ufrag:');
|
|
const pwd = lookup('a=ice-pwd:');
|
|
|
|
if (!ufrag || !pwd) {
|
|
throw Error('Failed parsing SDP: no ICE ufrag or pwd');
|
|
}
|
|
|
|
return {
|
|
fingerprints: [
|
|
{
|
|
fingerprint,
|
|
hash,
|
|
setup: isP2p ? setup! : 'active',
|
|
},
|
|
],
|
|
pwd,
|
|
ufrag,
|
|
...(sourceAudio && { ssrc: toTelegramSource(sourceAudio) }),
|
|
...(rawSourceVideo && {
|
|
'ssrc-groups': [
|
|
{
|
|
semantics: rawSourceVideo[0],
|
|
sources: rawSourceVideo.slice(1, rawSourceVideo.length).map(Number).map(toTelegramSource),
|
|
},
|
|
(isP2p && rawSourceScreencast && {
|
|
semantics: rawSourceScreencast[0],
|
|
sources: rawSourceScreencast.slice(1, rawSourceScreencast.length).map(Number).map(toTelegramSource),
|
|
}),
|
|
].filter(Boolean) as SsrcGroup[],
|
|
}),
|
|
...(isP2p && {
|
|
audioExtmap: parseExtmaps('audio'),
|
|
videoExtmap: parseExtmaps('video'),
|
|
screencastExtmap: parseExtmaps('screencast'),
|
|
audioPayloadTypes: parsePayloadTypes('audio'),
|
|
videoPayloadTypes: parsePayloadTypes('video'),
|
|
screencastPayloadTypes: parsePayloadTypes('screencast'),
|
|
}),
|
|
};
|
|
};
|