Created
June 19, 2020 19:22
-
-
Save nazar-pc/58ffd40db680ecae07198b15b919057e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {MediaKind, RtpCodecParameters, RtpParameters} from "mediasoup/lib/RtpParameters"; | |
import {DtlsFingerprint, DtlsParameters, IceCandidate, IceParameters} from "mediasoup/lib/WebRtcTransport"; | |
export interface ISdpConsumer { | |
id: string; | |
kind: MediaKind; | |
rtpParameters: RtpParameters; | |
removed: boolean; | |
} | |
export interface ITransportParameters { | |
dtlsParameters: DtlsParameters; | |
iceCandidates: IceCandidate[]; | |
iceParameters: IceParameters; | |
} | |
const RTP_PROBATOR_MID = 'probator'; | |
const RTP_PROBATOR_SSRC = 1234; | |
const RTP_PROBATOR_CODEC_PAYLOAD_TYPE = 127; | |
/** | |
* Hacky, but straightforward and supposedly correct SDP generator | |
* | |
* Tailored to mimic necessary mediasoup-client behavior | |
* Doesn't support data channels, encrypted header extensions and header extensions parameters and many other things | |
* | |
* @param transportParameters | |
* @param consumers | |
* @param counter Must start with 1 and increase by 1 with each new call for the same connection | |
*/ | |
export function generateSdp(transportParameters: ITransportParameters, consumers: ISdpConsumer[], counter: number): string { | |
consumers = withProbator(consumers); | |
const dtlsFingerprint = transportParameters.dtlsParameters.fingerprints[transportParameters.dtlsParameters.fingerprints.length - 1] as DtlsFingerprint; | |
const bundleElements = consumers.map(({removed, rtpParameters}, index) => removed && index !== 0 ? null : rtpParameters.mid).filter(Boolean).join(' '); | |
let sdp = ` | |
v=0 | |
o=- 10000 ${counter} IN IP4 0.0.0.0 | |
s=- | |
t=0 0 | |
a=ice-lite | |
a=fingerprint:${dtlsFingerprint.algorithm} ${dtlsFingerprint.value} | |
a=msid-semantic: WMS * | |
a=group:BUNDLE ${bundleElements} | |
`; | |
let firstSection = true; | |
for (const {id, kind, removed, rtpParameters} of consumers) { | |
const port = removed && !firstSection ? 0 : 7; | |
const payloadTypes = rtpParameters.codecs.map((codec) => codec.payloadType).join(' '); | |
sdp += ` | |
m=${kind} ${port} UDP/TLS/RTP/SAVPF ${payloadTypes} | |
c=IN IP4 127.0.0.1 | |
`; | |
for (const codec of rtpParameters.codecs) { | |
const encodingName = codec.mimeType.split('/').pop(); | |
const audioChannels = codec.channels ? '/' + codec.channels : ''; | |
sdp += ` | |
a=rtpmap:${codec.payloadType} ${encodingName}/${codec.clockRate}${audioChannels} | |
`; | |
} | |
for (const codec of rtpParameters.codecs) { | |
if (Object.keys(codec.parameters).length) { | |
const parameters: string[] = []; | |
for (const [key, value] of Object.entries(codec.parameters)) { | |
parameters.push(`${key}=${value}`); | |
} | |
sdp += ` | |
a=fmtp:${codec.payloadType} ${parameters.join(';')} | |
`; | |
} | |
} | |
for (const codec of rtpParameters.codecs) { | |
if (codec.rtcpFeedback) { | |
for (const rtcpFeedback of codec.rtcpFeedback) { | |
const feedback = rtcpFeedback.type + (rtcpFeedback.parameter ? ' ' + rtcpFeedback.parameter : ''); | |
sdp += ` | |
a=rtcp-fb:${codec.payloadType} ${feedback} | |
`; | |
} | |
} | |
} | |
if (!removed) { | |
for (const headerExtension of rtpParameters.headerExtensions ?? []) { | |
if (headerExtension.encrypt) { | |
// Not supported | |
continue; | |
} | |
// Parameters are ignored currently | |
sdp += ` | |
a=extmap:${headerExtension.id} ${headerExtension.uri} | |
`; | |
} | |
} | |
sdp += ` | |
a=setup:actpass | |
a=mid:${rtpParameters.mid} | |
a=msid:${rtpParameters.rtcp?.cname} ${id} | |
a=${removed ? 'inactive' : 'sendonly'} | |
a=ice-ufrag:${transportParameters.iceParameters.usernameFragment} | |
a=ice-pwd:${transportParameters.iceParameters.password} | |
`; | |
for (const candidate of transportParameters.iceCandidates) { | |
sdp += ` | |
a=candidate:${candidate.foundation} 1 ${candidate.protocol} ${candidate.priority} ${candidate.ip} ${candidate.port} typ ${candidate.type} | |
`; | |
} | |
sdp += ` | |
a=end-of-candidates | |
a=ice-options:renomination | |
`; | |
if (!removed) { | |
for (const encoding of rtpParameters.encodings ?? []) { | |
sdp += ` | |
a=ssrc:${encoding.ssrc} cname:${rtpParameters.rtcp?.cname} | |
`; | |
if (encoding.rtx) { | |
sdp += ` | |
a=ssrc:${encoding.rtx.ssrc} cname:${rtpParameters.rtcp?.cname} | |
a=ssrc-group:FID ${encoding.ssrc} ${encoding.rtx.ssrc} | |
`; | |
} | |
} | |
} | |
if (rtpParameters.rtcp?.mux) { | |
sdp += ` | |
a=rtcp-mux | |
`; | |
} | |
if (rtpParameters.rtcp?.reducedSize) { | |
sdp += ` | |
a=rtcp-rsize | |
`; | |
} | |
firstSection = false; | |
} | |
return sdp.trimLeft().replace(/\n+/g, '\n'); | |
} | |
export function extractDtlsFromSdp(sdp: string): DtlsParameters { | |
const match = sdp.match(/a=fingerprint:([^\s]+) ([^\s]+)/); | |
if (!match) { | |
throw new Error('Unexpected SDP contents, cannot find DTLS parameters'); | |
} | |
return { | |
// tslint:disable:object-literal-sort-keys | |
role: 'client', | |
fingerprints: [{ | |
algorithm: match[1], | |
value: match[2], | |
}], | |
// tslint:enablle:object-literal-sort-keys | |
}; | |
} | |
function withProbator(consumers: ISdpConsumer[]): ISdpConsumer[] { | |
consumers = clone(consumers); | |
const index = consumers.findIndex(({kind}) => { | |
return kind === 'video'; | |
}); | |
// This is a modified version of what is inside of mediasoup-client | |
if (index !== -1) { | |
const firstVideoConsumer = consumers[index]; | |
const codec: RtpCodecParameters = clone( | |
firstVideoConsumer.rtpParameters.codecs[0], | |
); | |
codec.payloadType = RTP_PROBATOR_CODEC_PAYLOAD_TYPE; | |
consumers.splice(index + 1, 0, { | |
id: 'probator', | |
kind: 'video', | |
removed: false, | |
rtpParameters: { | |
// tslint:disable:object-literal-sort-keys | |
codecs: [codec], | |
headerExtensions: firstVideoConsumer.rtpParameters.headerExtensions, | |
encodings: [{ | |
ssrc: RTP_PROBATOR_SSRC, | |
}], | |
rtcp: { | |
cname: "probator", | |
reducedSize: true, | |
mux: true, | |
}, | |
mid: RTP_PROBATOR_MID, | |
// tslint:enablle:object-literal-sort-keys | |
}, | |
}); | |
} | |
return consumers; | |
} | |
function clone<T>(o: T): T { | |
return JSON.parse(JSON.stringify(o)); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment