UNPKG

mediasoup-client

Version:

mediasoup client side TypeScript library

304 lines (303 loc) 12 kB
"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;