mediasoup-client
Version:
mediasoup client side TypeScript library
304 lines (303 loc) • 12 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RemoteSdp = void 0;
const sdpTransform = require("sdp-transform");
const Logger_1 = require("../../Logger");
const MediaSection_1 = require("./MediaSection");
const logger = new Logger_1.Logger('RemoteSdp');
class RemoteSdp {
// Remote ICE parameters.
_iceParameters;
// Remote ICE candidates.
_iceCandidates;
// Remote DTLS parameters.
_dtlsParameters;
// Remote SCTP parameters.
_sctpParameters;
// Parameters for plain RTP (no SRTP nor DTLS no BUNDLE).
_plainRtpParameters;
// Whether this is Plan-B SDP.
_planB;
// MediaSection instances with same order as in the SDP.
_mediaSections = [];
// MediaSection indices indexed by MID.
_midToIndex = new Map();
// First MID.
_firstMid;
// SDP object.
_sdpObject;
constructor({ iceParameters, iceCandidates, dtlsParameters, sctpParameters, plainRtpParameters, planB = false, }) {
this._iceParameters = iceParameters;
this._iceCandidates = iceCandidates;
this._dtlsParameters = dtlsParameters;
this._sctpParameters = sctpParameters;
this._plainRtpParameters = plainRtpParameters;
this._planB = planB;
this._sdpObject = {
version: 0,
origin: {
address: '0.0.0.0',
ipVer: 4,
netType: 'IN',
sessionId: 10000,
sessionVersion: 0,
username: 'mediasoup-client',
},
name: '-',
timing: { start: 0, stop: 0 },
media: [],
};
// If ICE parameters are given, add ICE-Lite indicator.
if (iceParameters?.iceLite) {
this._sdpObject.icelite = 'ice-lite';
}
// If DTLS parameters are given, assume WebRTC and BUNDLE.
if (dtlsParameters) {
this._sdpObject.msidSemantic = { semantic: 'WMS', token: '*' };
// NOTE: We take the latest fingerprint.
const numFingerprints = this._dtlsParameters.fingerprints.length;
this._sdpObject.fingerprint = {
type: dtlsParameters.fingerprints[numFingerprints - 1].algorithm,
hash: dtlsParameters.fingerprints[numFingerprints - 1].value,
};
this._sdpObject.groups = [{ type: 'BUNDLE', mids: '' }];
}
// If there are plain RPT parameters, override SDP origin.
if (plainRtpParameters) {
this._sdpObject.origin.address = plainRtpParameters.ip;
this._sdpObject.origin.ipVer = plainRtpParameters.ipVersion;
}
}
updateIceParameters(iceParameters) {
logger.debug('updateIceParameters() [iceParameters:%o]', iceParameters);
this._iceParameters = iceParameters;
this._sdpObject.icelite = iceParameters.iceLite ? 'ice-lite' : undefined;
for (const mediaSection of this._mediaSections) {
mediaSection.setIceParameters(iceParameters);
}
}
updateDtlsRole(role) {
logger.debug('updateDtlsRole() [role:%s]', role);
this._dtlsParameters.role = role;
for (const mediaSection of this._mediaSections) {
mediaSection.setDtlsRole(role);
}
}
/**
* Set session level a=extmap-allow-mixed attibute.
*/
setSessionExtmapAllowMixed() {
logger.debug('setSessionExtmapAllowMixed()');
this._sdpObject.extmapAllowMixed = 'extmap-allow-mixed';
}
getNextMediaSectionIdx() {
// If a closed media section is found, return its index.
for (let idx = 0; idx < this._mediaSections.length; ++idx) {
const mediaSection = this._mediaSections[idx];
if (mediaSection.closed) {
return { idx, reuseMid: mediaSection.mid };
}
}
// If no closed media section is found, return next one.
return { idx: this._mediaSections.length };
}
send({ offerMediaObject, reuseMid, offerRtpParameters, answerRtpParameters, codecOptions, }) {
const mediaSection = new MediaSection_1.AnswerMediaSection({
iceParameters: this._iceParameters,
iceCandidates: this._iceCandidates,
dtlsParameters: this._dtlsParameters,
plainRtpParameters: this._plainRtpParameters,
planB: this._planB,
offerMediaObject,
offerRtpParameters,
answerRtpParameters,
codecOptions,
});
// Unified-Plan with closed media section replacement.
if (reuseMid) {
this._replaceMediaSection(mediaSection, reuseMid);
}
// Unified-Plan or Plan-B with different media kind.
else if (!this._midToIndex.has(mediaSection.mid)) {
this._addMediaSection(mediaSection);
}
// Plan-B with same media kind.
else {
this._replaceMediaSection(mediaSection);
}
}
receive({ mid, kind, offerRtpParameters, streamId, trackId, }) {
const idx = this._midToIndex.get(mid);
let mediaSection;
if (idx !== undefined) {
mediaSection = this._mediaSections[idx];
}
// Allow both 1 byte and 2 bytes length header extensions since
// mediasoup can send both at any time.
this.setSessionExtmapAllowMixed();
// Unified-Plan or different media kind.
if (!mediaSection) {
mediaSection = new MediaSection_1.OfferMediaSection({
iceParameters: this._iceParameters,
iceCandidates: this._iceCandidates,
dtlsParameters: this._dtlsParameters,
plainRtpParameters: this._plainRtpParameters,
planB: this._planB,
mid,
kind,
offerRtpParameters,
streamId,
trackId,
});
// Let's try to recycle a closed media section (if any).
// NOTE: Yes, we can recycle a closed m=audio section with a new m=video.
const oldMediaSection = this._mediaSections.find(m => m.closed);
if (oldMediaSection) {
this._replaceMediaSection(mediaSection, oldMediaSection.mid);
}
else {
this._addMediaSection(mediaSection);
}
}
// Plan-B.
else {
mediaSection.planBReceive({ offerRtpParameters, streamId, trackId });
this._replaceMediaSection(mediaSection);
}
}
pauseMediaSection(mid) {
const mediaSection = this._findMediaSection(mid);
mediaSection.pause();
}
resumeSendingMediaSection(mid) {
const mediaSection = this._findMediaSection(mid);
mediaSection.resume();
}
resumeReceivingMediaSection(mid) {
const mediaSection = this._findMediaSection(mid);
mediaSection.resume();
}
disableMediaSection(mid) {
const mediaSection = this._findMediaSection(mid);
mediaSection.disable();
}
/**
* Closes media section. Returns true if the given MID corresponds to a m
* section that has been indeed closed. False otherwise.
*
* NOTE: Closing the first m section is a pain since it invalidates the bundled
* transport, so instead closing it we just disable it.
*/
closeMediaSection(mid) {
const mediaSection = this._findMediaSection(mid);
// NOTE: Closing the first m section is a pain since it invalidates the
// bundled transport, so let's avoid it.
if (mid === this._firstMid) {
logger.debug('closeMediaSection() | cannot close first media section, disabling it instead [mid:%s]', mid);
this.disableMediaSection(mid);
return false;
}
mediaSection.close();
// Regenerate BUNDLE mids.
this._regenerateBundleMids();
return true;
}
muxMediaSectionSimulcast(mid, encodings) {
const mediaSection = this._findMediaSection(mid);
mediaSection.muxSimulcastStreams(encodings);
this._replaceMediaSection(mediaSection);
}
planBStopReceiving({ mid, offerRtpParameters, }) {
const mediaSection = this._findMediaSection(mid);
mediaSection.planBStopReceiving({ offerRtpParameters });
this._replaceMediaSection(mediaSection);
}
sendSctpAssociation({ offerMediaObject }) {
const mediaSection = new MediaSection_1.AnswerMediaSection({
iceParameters: this._iceParameters,
iceCandidates: this._iceCandidates,
dtlsParameters: this._dtlsParameters,
sctpParameters: this._sctpParameters,
plainRtpParameters: this._plainRtpParameters,
offerMediaObject,
});
this._addMediaSection(mediaSection);
}
receiveSctpAssociation({ oldDataChannelSpec = false, } = {}) {
const mediaSection = new MediaSection_1.OfferMediaSection({
iceParameters: this._iceParameters,
iceCandidates: this._iceCandidates,
dtlsParameters: this._dtlsParameters,
sctpParameters: this._sctpParameters,
plainRtpParameters: this._plainRtpParameters,
mid: 'datachannel',
kind: 'application',
oldDataChannelSpec,
});
this._addMediaSection(mediaSection);
}
getSdp() {
// Increase SDP version.
this._sdpObject.origin.sessionVersion++;
return sdpTransform.write(this._sdpObject);
}
_addMediaSection(newMediaSection) {
if (!this._firstMid) {
this._firstMid = newMediaSection.mid;
}
// Add to the vector.
this._mediaSections.push(newMediaSection);
// Add to the map.
this._midToIndex.set(newMediaSection.mid, this._mediaSections.length - 1);
// Add to the SDP object.
this._sdpObject.media.push(newMediaSection.getObject());
// Regenerate BUNDLE mids.
this._regenerateBundleMids();
}
_replaceMediaSection(newMediaSection, reuseMid) {
// Store it in the map.
if (typeof reuseMid === 'string') {
const idx = this._midToIndex.get(reuseMid);
if (idx === undefined) {
throw new Error(`no media section found for reuseMid '${reuseMid}'`);
}
const oldMediaSection = this._mediaSections[idx];
// Replace the index in the vector with the new media section.
this._mediaSections[idx] = newMediaSection;
// Update the map.
this._midToIndex.delete(oldMediaSection.mid);
this._midToIndex.set(newMediaSection.mid, idx);
// Update the SDP object.
this._sdpObject.media[idx] = newMediaSection.getObject();
// Regenerate BUNDLE mids.
this._regenerateBundleMids();
}
else {
const idx = this._midToIndex.get(newMediaSection.mid);
if (idx === undefined) {
throw new Error(`no media section found with mid '${newMediaSection.mid}'`);
}
// Replace the index in the vector with the new media section.
this._mediaSections[idx] = newMediaSection;
// Update the SDP object.
this._sdpObject.media[idx] = newMediaSection.getObject();
}
}
_findMediaSection(mid) {
const idx = this._midToIndex.get(mid);
if (idx === undefined) {
throw new Error(`no media section found with mid '${mid}'`);
}
return this._mediaSections[idx];
}
_regenerateBundleMids() {
if (!this._dtlsParameters) {
return;
}
this._sdpObject.groups[0].mids = this._mediaSections
.filter((mediaSection) => !mediaSection.closed)
.map((mediaSection) => mediaSection.mid)
.join(' ');
}
}
exports.RemoteSdp = RemoteSdp;