UNPKG

msc-node

Version:

mediasoup client side Node.js library

284 lines (283 loc) 11.8 kB
"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;