UNPKG

msc-node

Version:

mediasoup client side Node.js library

535 lines (534 loc) 25.2 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; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Safari12 = void 0; const sdpTransform = __importStar(require("sdp-transform")); const Logger_1 = require("../Logger"); const utils = __importStar(require("../utils")); const ortc = __importStar(require("../ortc")); const sdpCommonUtils = __importStar(require("./sdp/commonUtils")); const sdpUnifiedPlanUtils = __importStar(require("./sdp/unifiedPlanUtils")); const HandlerInterface_1 = require("./HandlerInterface"); const RemoteSdp_1 = require("./sdp/RemoteSdp"); const logger = new Logger_1.Logger('Safari12'); const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 }; class Safari12 extends HandlerInterface_1.HandlerInterface { /** * Creates a factory function. */ static createFactory() { return () => new Safari12(); } constructor() { super(); // Map of RTCTransceivers indexed by MID. this._mapMidTransceiver = new Map(); // Local stream for sending. this._sendStream = new MediaStream(); // Whether a DataChannel m=application section has been created. this._hasDataChannelMediaSection = false; // Sending DataChannel id value counter. Incremented for each new DataChannel. this._nextSendSctpStreamId = 0; // Got transport local and remote parameters. this._transportReady = false; } get name() { return 'Safari12'; } close() { logger.debug('close()'); // Close RTCPeerConnection. if (this._pc) { try { this._pc.close(); } catch (error) { } } } getNativeRtpCapabilities() { return __awaiter(this, void 0, void 0, function* () { logger.debug('getNativeRtpCapabilities()'); const pc = new RTCPeerConnection({ iceServers: [], iceTransportPolicy: 'all', bundlePolicy: 'max-bundle', rtcpMuxPolicy: 'require' }); try { pc.addTransceiver('audio'); pc.addTransceiver('video'); const offer = yield pc.createOffer(); try { pc.close(); } catch (error) { } const sdpObject = sdpTransform.parse(offer.sdp); const nativeRtpCapabilities = sdpCommonUtils.extractRtpCapabilities({ sdpObject }); return nativeRtpCapabilities; } catch (error) { try { pc.close(); } catch (error2) { } throw error; } }); } getNativeSctpCapabilities() { return __awaiter(this, void 0, void 0, function* () { logger.debug('getNativeSctpCapabilities()'); return { numStreams: SCTP_NUM_STREAMS }; }); } run({ direction, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, extendedRtpCapabilities }) { logger.debug('run()'); this._direction = direction; this._remoteSdp = new RemoteSdp_1.RemoteSdp({ iceParameters, iceCandidates, dtlsParameters, sctpParameters }); this._sendingRtpParametersByKind = { audio: ortc.getSendingRtpParameters('audio', extendedRtpCapabilities), video: ortc.getSendingRtpParameters('video', extendedRtpCapabilities) }; this._sendingRemoteRtpParametersByKind = { audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) }; this._pc = new RTCPeerConnection(Object.assign({ iceServers: iceServers || [], iceTransportPolicy: iceTransportPolicy || 'all', bundlePolicy: 'max-bundle', rtcpMuxPolicy: 'require' }, additionalSettings), proprietaryConstraints); // Handle RTCPeerConnection connection status. this._pc.addEventListener('iceconnectionstatechange', () => { switch (this._pc.iceConnectionState) { case 'checking': this.emit('@connectionstatechange', 'connecting'); break; case 'connected': case 'completed': this.emit('@connectionstatechange', 'connected'); break; case 'failed': this.emit('@connectionstatechange', 'failed'); break; case 'disconnected': this.emit('@connectionstatechange', 'disconnected'); break; case 'closed': this.emit('@connectionstatechange', 'closed'); break; } }); } updateIceServers(iceServers) { return __awaiter(this, void 0, void 0, function* () { logger.debug('updateIceServers()'); const configuration = this._pc.getConfiguration(); configuration.iceServers = iceServers; this._pc.setConfiguration(configuration); }); } restartIce(iceParameters) { return __awaiter(this, void 0, void 0, function* () { logger.debug('restartIce()'); // Provide the remote SDP handler with new remote ICE parameters. this._remoteSdp.updateIceParameters(iceParameters); if (!this._transportReady) { return; } if (this._direction === 'send') { const offer = yield this._pc.createOffer({ iceRestart: true }); logger.debug('restartIce() | calling pc.setLocalDescription() [offer:%o]', offer); yield this._pc.setLocalDescription(offer); const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; logger.debug('restartIce() | calling pc.setRemoteDescription() [answer:%o]', answer); yield this._pc.setRemoteDescription(answer); } else { const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; logger.debug('restartIce() | calling pc.setRemoteDescription() [offer:%o]', offer); yield this._pc.setRemoteDescription(offer); const answer = yield this._pc.createAnswer(); logger.debug('restartIce() | calling pc.setLocalDescription() [answer:%o]', answer); yield this._pc.setLocalDescription(answer); } }); } getTransportStats() { return __awaiter(this, void 0, void 0, function* () { return this._pc.getStats(); }); } send({ track, encodings, codecOptions, codec }) { return __awaiter(this, void 0, void 0, function* () { this._assertSendDirection(); logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); const sendingRtpParameters = utils.clone(this._sendingRtpParametersByKind[track.kind], {}); // This may throw. sendingRtpParameters.codecs = ortc.reduceCodecs(sendingRtpParameters.codecs, codec); const sendingRemoteRtpParameters = utils.clone(this._sendingRemoteRtpParametersByKind[track.kind], {}); // This may throw. sendingRemoteRtpParameters.codecs = ortc.reduceCodecs(sendingRemoteRtpParameters.codecs, codec); const mediaSectionIdx = this._remoteSdp.getNextMediaSectionIdx(); const transceiver = this._pc.addTransceiver(track, { direction: 'sendonly', streams: [this._sendStream] }); let offer = yield this._pc.createOffer(); let localSdpObject = sdpTransform.parse(offer.sdp); let offerMediaObject; if (!this._transportReady) { yield this._setupTransport({ localDtlsRole: 'server', localSdpObject }); } if (encodings && encodings.length > 1) { logger.debug('send() | enabling legacy simulcast'); localSdpObject = sdpTransform.parse(offer.sdp); offerMediaObject = localSdpObject.media[mediaSectionIdx.idx]; sdpUnifiedPlanUtils.addLegacySimulcast({ offerMediaObject, numStreams: encodings.length }); offer = { type: 'offer', sdp: sdpTransform.write(localSdpObject) }; } logger.debug('send() | calling pc.setLocalDescription() [offer:%o]', offer); yield this._pc.setLocalDescription(offer); // We can now get the transceiver.mid. const localId = transceiver.mid; // Set MID. sendingRtpParameters.mid = localId; localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); offerMediaObject = localSdpObject.media[mediaSectionIdx.idx]; // Set RTCP CNAME. sendingRtpParameters.rtcp.cname = sdpCommonUtils.getCname({ offerMediaObject }); // Set RTP encodings. sendingRtpParameters.encodings = sdpUnifiedPlanUtils.getRtpEncodings({ offerMediaObject }); // Complete encodings with given values. if (encodings) { for (let idx = 0; idx < sendingRtpParameters.encodings.length; ++idx) { if (encodings[idx]) { Object.assign(sendingRtpParameters.encodings[idx], encodings[idx]); } } } // If VP8 or H264 and there is effective simulcast, add scalabilityMode to // each encoding. if (sendingRtpParameters.encodings.length > 1 && (sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8' || sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/h264')) { for (const encoding of sendingRtpParameters.encodings) { encoding.scalabilityMode = 'S1T3'; } } this._remoteSdp.send({ offerMediaObject, reuseMid: mediaSectionIdx.reuseMid, offerRtpParameters: sendingRtpParameters, answerRtpParameters: sendingRemoteRtpParameters, codecOptions }); const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer); yield this._pc.setRemoteDescription(answer); // Store in the map. this._mapMidTransceiver.set(localId, transceiver); return { localId, rtpParameters: sendingRtpParameters, rtpSender: transceiver.sender }; }); } stopSending(localId) { return __awaiter(this, void 0, void 0, function* () { this._assertSendDirection(); logger.debug('stopSending() [localId:%s]', localId); const transceiver = this._mapMidTransceiver.get(localId); if (!transceiver) { throw new Error('associated RTCRtpTransceiver not found'); } transceiver.sender.replaceTrack(null); this._pc.removeTrack(transceiver.sender); this._remoteSdp.closeMediaSection(transceiver.mid); const offer = yield this._pc.createOffer(); logger.debug('stopSending() | calling pc.setLocalDescription() [offer:%o]', offer); yield this._pc.setLocalDescription(offer); const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer); yield this._pc.setRemoteDescription(answer); }); } replaceTrack(localId, track) { return __awaiter(this, void 0, void 0, function* () { this._assertSendDirection(); if (track) { logger.debug('replaceTrack() [localId:%s, track.id:%s]', localId, track.id); } else { logger.debug('replaceTrack() [localId:%s, no track]', localId); } const transceiver = this._mapMidTransceiver.get(localId); if (!transceiver) { throw new Error('associated RTCRtpTransceiver not found'); } yield transceiver.sender.replaceTrack(track); }); } setMaxSpatialLayer(localId, spatialLayer) { return __awaiter(this, void 0, void 0, function* () { this._assertSendDirection(); logger.debug('setMaxSpatialLayer() [localId:%s, spatialLayer:%s]', localId, spatialLayer); const transceiver = this._mapMidTransceiver.get(localId); if (!transceiver) { throw new Error('associated RTCRtpTransceiver not found'); } const parameters = transceiver.sender.getParameters(); parameters.encodings.forEach((encoding, idx) => { if (idx <= spatialLayer) { encoding.active = true; } else { encoding.active = false; } }); yield transceiver.sender.setParameters(parameters); }); } setRtpEncodingParameters(localId, params) { return __awaiter(this, void 0, void 0, function* () { this._assertSendDirection(); logger.debug('setRtpEncodingParameters() [localId:%s, params:%o]', localId, params); const transceiver = this._mapMidTransceiver.get(localId); if (!transceiver) { throw new Error('associated RTCRtpTransceiver not found'); } const parameters = transceiver.sender.getParameters(); parameters.encodings.forEach((encoding, idx) => { parameters.encodings[idx] = Object.assign(Object.assign({}, encoding), params); }); yield transceiver.sender.setParameters(parameters); }); } getSenderStats(localId) { return __awaiter(this, void 0, void 0, function* () { this._assertSendDirection(); const transceiver = this._mapMidTransceiver.get(localId); if (!transceiver) { throw new Error('associated RTCRtpTransceiver not found'); } return transceiver.sender.getStats(); }); } sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol, priority }) { return __awaiter(this, void 0, void 0, function* () { this._assertSendDirection(); const options = { negotiated: true, id: this._nextSendSctpStreamId, ordered, maxPacketLifeTime, maxRetransmits, protocol, priority }; logger.debug('sendDataChannel() [options:%o]', options); const dataChannel = this._pc.createDataChannel(label, options); // Increase next id. this._nextSendSctpStreamId = ++this._nextSendSctpStreamId % SCTP_NUM_STREAMS.MIS; // If this is the first DataChannel we need to create the SDP answer with // m=application section. if (!this._hasDataChannelMediaSection) { const offer = yield this._pc.createOffer(); const localSdpObject = sdpTransform.parse(offer.sdp); const offerMediaObject = localSdpObject.media .find((m) => m.type === 'application'); if (!this._transportReady) { yield this._setupTransport({ localDtlsRole: 'server', localSdpObject }); } logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); yield this._pc.setLocalDescription(offer); this._remoteSdp.sendSctpAssociation({ offerMediaObject }); const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; logger.debug('sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); yield this._pc.setRemoteDescription(answer); this._hasDataChannelMediaSection = true; } const sctpStreamParameters = { streamId: options.id, ordered: options.ordered, maxPacketLifeTime: options.maxPacketLifeTime, maxRetransmits: options.maxRetransmits }; return { dataChannel, sctpStreamParameters }; }); } receive({ trackId, kind, rtpParameters }) { return __awaiter(this, void 0, void 0, function* () { this._assertRecvDirection(); logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); const localId = rtpParameters.mid || String(this._mapMidTransceiver.size); this._remoteSdp.receive({ mid: localId, kind, offerRtpParameters: rtpParameters, streamId: rtpParameters.rtcp.cname, trackId }); const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; logger.debug('receive() | calling pc.setRemoteDescription() [offer:%o]', offer); yield this._pc.setRemoteDescription(offer); let answer = yield this._pc.createAnswer(); const localSdpObject = sdpTransform.parse(answer.sdp); const answerMediaObject = localSdpObject.media .find((m) => String(m.mid) === localId); // May need to modify codec parameters in the answer based on codec // parameters in the offer. sdpCommonUtils.applyCodecParameters({ offerRtpParameters: rtpParameters, answerMediaObject }); answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; if (!this._transportReady) { yield this._setupTransport({ localDtlsRole: 'client', localSdpObject }); } logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); yield this._pc.setLocalDescription(answer); const transceiver = this._pc.getTransceivers() .find((t) => t.mid === localId); if (!transceiver) { throw new Error('new RTCRtpTransceiver not found'); } // Store in the map. this._mapMidTransceiver.set(localId, transceiver); return { localId, track: transceiver.receiver.track, rtpReceiver: transceiver.receiver }; }); } stopReceiving(localId) { return __awaiter(this, void 0, void 0, function* () { this._assertRecvDirection(); logger.debug('stopReceiving() [localId:%s]', localId); const transceiver = this._mapMidTransceiver.get(localId); if (!transceiver) { throw new Error('associated RTCRtpTransceiver not found'); } this._remoteSdp.closeMediaSection(transceiver.mid); const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; logger.debug('stopReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); yield this._pc.setRemoteDescription(offer); const answer = yield this._pc.createAnswer(); logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); yield this._pc.setLocalDescription(answer); }); } getReceiverStats(localId) { return __awaiter(this, void 0, void 0, function* () { this._assertRecvDirection(); const transceiver = this._mapMidTransceiver.get(localId); if (!transceiver) { throw new Error('associated RTCRtpTransceiver not found'); } return transceiver.receiver.getStats(); }); } receiveDataChannel({ sctpStreamParameters, label, protocol }) { return __awaiter(this, void 0, void 0, function* () { this._assertRecvDirection(); const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; const options = { negotiated: true, id: streamId, ordered, maxPacketLifeTime, maxRetransmits, protocol }; logger.debug('receiveDataChannel() [options:%o]', options); const dataChannel = this._pc.createDataChannel(label, options); // If this is the first DataChannel we need to create the SDP offer with // m=application section. if (!this._hasDataChannelMediaSection) { this._remoteSdp.receiveSctpAssociation(); const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [offer:%o]', offer); yield this._pc.setRemoteDescription(offer); const answer = yield this._pc.createAnswer(); if (!this._transportReady) { const localSdpObject = sdpTransform.parse(answer.sdp); yield this._setupTransport({ localDtlsRole: 'client', localSdpObject }); } logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); yield this._pc.setLocalDescription(answer); this._hasDataChannelMediaSection = true; } return { dataChannel }; }); } _setupTransport({ localDtlsRole, localSdpObject }) { return __awaiter(this, void 0, void 0, function* () { if (!localSdpObject) { localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); } // Get our local DTLS parameters. const dtlsParameters = sdpCommonUtils.extractDtlsParameters({ sdpObject: localSdpObject }); // Set our DTLS role. dtlsParameters.role = localDtlsRole; // Update the remote DTLS role in the SDP. this._remoteSdp.updateDtlsRole(localDtlsRole === 'client' ? 'server' : 'client'); // Need to tell the remote transport about our parameters. yield this.safeEmitAsPromise('@connect', { dtlsParameters }); this._transportReady = true; }); } _assertSendDirection() { if (this._direction !== 'send') { throw new Error('method can just be called for handlers with "send" direction'); } } _assertRecvDirection() { if (this._direction !== 'recv') { throw new Error('method can just be called for handlers with "recv" direction'); } } } exports.Safari12 = Safari12;