msc-node
Version:
mediasoup client side Node.js library
284 lines (283 loc) • 11.8 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RemoteSdp = void 0;
const sdpTransform = __importStar(require("sdp-transform"));
const Logger_1 = require("../../Logger");
const MediaSection_1 = require("./MediaSection");
const logger = new Logger_1.Logger('RemoteSdp');
class RemoteSdp {
constructor({ iceParameters, iceCandidates, dtlsParameters, sctpParameters, plainRtpParameters, planB = false }) {
// MediaSection instances with same order as in the SDP.
this._mediaSections = [];
// MediaSection indices indexed by MID.
this._midToIndex = new Map();
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 && 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);
}
}
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, extmapAllowMixed = false }) {
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,
extmapAllowMixed
});
// 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];
}
// 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);
}
}
disableMediaSection(mid) {
const idx = this._midToIndex.get(mid);
if (idx === undefined) {
throw new Error(`no media section found with mid '${mid}'`);
}
const mediaSection = this._mediaSections[idx];
mediaSection.disable();
}
closeMediaSection(mid) {
const idx = this._midToIndex.get(mid);
if (idx === undefined) {
throw new Error(`no media section found with mid '${mid}'`);
}
const mediaSection = this._mediaSections[idx];
// 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;
}
mediaSection.close();
// Regenerate BUNDLE mids.
this._regenerateBundleMids();
}
planBStopReceiving({ mid, offerRtpParameters }) {
const idx = this._midToIndex.get(mid);
if (idx === undefined) {
throw new Error(`no media section found with mid '${mid}'`);
}
const mediaSection = this._mediaSections[idx];
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();
}
}
_regenerateBundleMids() {
if (!this._dtlsParameters) {
return;
}
this._sdpObject.groups[0].mids = this._mediaSections
.filter((mediaSection) => !mediaSection.closed)
.map((mediaSection) => mediaSection.mid)
.join(' ');
}
}
exports.RemoteSdp = RemoteSdp;