msc-node
Version:
mediasoup client side Node.js library
463 lines (462 loc) • 21.6 kB
JavaScript
"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;