UNPKG

msc-node

Version:

mediasoup client side Node.js library

463 lines (462 loc) 21.6 kB
"use strict"; /* eslint-disable */ 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.Werift = void 0; const werift_1 = require("werift"); const utils = __importStar(require("../utils")); const sdpTransform = __importStar(require("sdp-transform")); const sdpCommonUtils = __importStar(require("./sdp/commonUtils")); const sdpUnifiedPlanUtils = __importStar(require("./sdp/unifiedPlanUtils")); const ortc = __importStar(require("../ortc")); const RemoteSdp_1 = require("./sdp/RemoteSdp"); const Logger_1 = require("../Logger"); const HandlerInterface_1 = require("./HandlerInterface"); const logger = new Logger_1.Logger("werift"); const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 }; class Werift extends HandlerInterface_1.HandlerInterface { static createFactory(nativeRtpCapabilities) { return () => new Werift(nativeRtpCapabilities); } constructor(nativeRtpCapabilities) { var _a, _b; super(); this.nativeRtpCapabilities = nativeRtpCapabilities; // Map of RTCTransceivers indexed by MID. this._mapMidTransceiver = new Map(); this._hasDataChannelMediaSection = false; this._nextSendSctpStreamId = 0; this._transportReady = false; if (!this.nativeRtpCapabilities.headerExtensions) { this.nativeRtpCapabilities.headerExtensions = {}; } if (!((_a = this.nativeRtpCapabilities.headerExtensions) === null || _a === void 0 ? void 0 : _a.audio)) { this.nativeRtpCapabilities.headerExtensions.audio = []; } if (!((_b = this.nativeRtpCapabilities.headerExtensions) === null || _b === void 0 ? void 0 : _b.video)) { this.nativeRtpCapabilities.headerExtensions.video = []; } } get name() { return "werift"; } close() { logger.debug("close()"); // Close RTCPeerConnection. if (this._pc) { try { this._pc.close(); } catch (error) { } } } getNativeRtpCapabilities() { var _a, _b, _c, _d; return __awaiter(this, void 0, void 0, function* () { let preferredPayloadType = 96; const codecs = [ ...(this.nativeRtpCapabilities.codecs.video || []).map(({ mimeType, clockRate, rtcpFeedback, parameters }) => { const obj = parameters ? (0, werift_1.codecParametersFromString)(parameters) : {}; for (const key of Object.keys(obj)) { try { const n = Number(obj[key]); if (!Number.isNaN(n)) { obj[key] = n; } } catch (error) { } } const codec = { kind: "video", mimeType, clockRate, rtcpFeedback, preferredPayloadType: preferredPayloadType++, parameters: obj, }; return codec; }), ...(this.nativeRtpCapabilities.codecs.audio || []).map(({ mimeType, clockRate, channels }) => { const codec = { kind: "audio", mimeType, clockRate, channels, preferredPayloadType: preferredPayloadType++, }; return codec; }), ]; let preferredId = 1; const headerExtensions = [ ...((_b = (_a = this.nativeRtpCapabilities.headerExtensions) === null || _a === void 0 ? void 0 : _a.audio) !== null && _b !== void 0 ? _b : []).map(({ uri }) => { const ext = { uri, preferredId: preferredId++ }; return ext; }), ...((_d = (_c = this.nativeRtpCapabilities.headerExtensions) === null || _c === void 0 ? void 0 : _c.video) !== null && _d !== void 0 ? _d : []).map(({ uri }) => { const ext = { uri, preferredId: preferredId++ }; return ext; }), ]; const caps = { codecs, headerExtensions, }; return caps; }); } 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), }; const peerConfig = Object.assign(Object.assign({ iceServers: iceServers || [], iceTransportPolicy: iceTransportPolicy || "all", bundlePolicy: "max-bundle", rtcpMuxPolicy: "require", sdpSemantics: "unified-plan" }, this.nativeRtpCapabilities), additionalSettings); logger.debug("run() | calling new RTCPeerConnection() peerConfig", peerConfig); this._pc = new werift_1.RTCPeerConnection(peerConfig); // Handle RTCPeerConnection connection status. this._pc.connectionStateChange.subscribe((state) => { switch (state) { case "connecting": this.emit("@connectionstatechange", "connecting"); break; case "connected": this.emit("@connectionstatechange", "connected"); break; } }); } // todo impl updateIceServers(iceServers) { return __awaiter(this, void 0, void 0, function* () { }); } // todo impl restartIce(iceParameters) { return __awaiter(this, void 0, void 0, function* () { }); } // todo impl // @ts-expect-error getTransportStats() { return __awaiter(this, void 0, void 0, function* () { }); } 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); if (encodings && encodings.length > 1) { encodings.forEach((encoding, idx) => { encoding.rid = `r${idx}`; }); } 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", }); const offer = yield this._pc.createOffer(); let localSdpObject = sdpTransform.parse(offer.sdp); let offerMediaObject; if (!this._transportReady) { yield this._setupTransport({ localDtlsRole: "server", 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 by parsing the SDP offer if no encodings are given. if (!encodings) { sendingRtpParameters.encodings = sdpUnifiedPlanUtils.getRtpEncodings({ offerMediaObject, }); } // Set RTP encodings by parsing the SDP offer and complete them with given // one if just a single encoding has been given. else if (encodings.length === 1) { const newEncodings = sdpUnifiedPlanUtils.getRtpEncodings({ offerMediaObject, }); Object.assign(newEncodings[0], encodings[0]); sendingRtpParameters.encodings = newEncodings; } // Otherwise if more than 1 encoding are given use them verbatim. else { sendingRtpParameters.encodings = encodings; } this._remoteSdp.send({ offerMediaObject, reuseMid: mediaSectionIdx.reuseMid, offerRtpParameters: sendingRtpParameters, answerRtpParameters: sendingRemoteRtpParameters, codecOptions, extmapAllowMixed: true, }); 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: localId, rtpParameters: sendingRtpParameters, rtpSender: transceiver.sender, }; }); } // todo impl stopSending(localId) { return __awaiter(this, void 0, void 0, function* () { }); } // todo impl replaceTrack(localId, track) { return __awaiter(this, void 0, void 0, function* () { }); } // todo impl setMaxSpatialLayer(localId, spatialLayer) { return __awaiter(this, void 0, void 0, function* () { }); } // todo impl setRtpEncodingParameters(localId, params) { return __awaiter(this, void 0, void 0, function* () { }); } // todo impl // @ts-expect-error getSenderStats(localId) { return __awaiter(this, void 0, void 0, function* () { }); } 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: 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 = new werift_1.RTCSessionDescription(this._remoteSdp.getSdp(), "offer"); 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 = new werift_1.RTCSessionDescription(sdpTransform.write(localSdpObject), "answer"); 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, // todo fix track: transceiver.receiver.tracks[0], // todo fix 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 = new werift_1.RTCSessionDescription(this._remoteSdp.getSdp(), "offer"); 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); }); } // todo impl // @ts-expect-error getReceiverStats(localId) { return __awaiter(this, void 0, void 0, function* () { }); } 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.Werift = Werift;