mediasoup
Version:
Cutting Edge WebRTC Video Conferencing
160 lines (159 loc) • 6.67 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const node_dgram_1 = require("node:dgram");
const werift_sctp_1 = require("werift-sctp");
const mediasoup = require("../");
const enhancedEvents_1 = require("../enhancedEvents");
const ctx = {};
beforeEach(async () => {
ctx.worker = await mediasoup.createWorker({
disableLiburing: true,
});
ctx.router = await ctx.worker.createRouter();
ctx.plainTransport = await ctx.router.createPlainTransport({
// https://github.com/nodejs/node/issues/14900.
listenIp: '127.0.0.1',
// So we don't need to call plainTransport.connect().
comedia: true,
enableSctp: true,
numSctpStreams: { OS: 256, MIS: 256 },
});
// Create an explicit SCTP outgoing stream id.
ctx.sctpSendStreamId = 123;
ctx.sctpClient = werift_sctp_1.SCTP.client((0, werift_sctp_1.createUdpTransport)((0, node_dgram_1.createSocket)('udp4'), {
port: ctx.plainTransport.tuple.localPort,
address: ctx.plainTransport.tuple.localAddress,
}));
// Create a DataProducer with the corresponding SCTP stream id.
ctx.dataProducer = await ctx.plainTransport.produceData({
sctpStreamParameters: {
streamId: ctx.sctpSendStreamId,
ordered: true,
},
label: 'node-sctp',
protocol: 'foo & bar 😀😀😀',
});
// Create a DataConsumer to receive messages from the DataProducer over the
// same plainTransport.
ctx.dataConsumer = await ctx.plainTransport.consumeData({
dataProducerId: ctx.dataProducer.id,
});
let connectionTimeoutTimer;
await Promise.race([
// Wait for SCTP to become connected in both the PlainTransport and in the
// werift-sctp client.
Promise.all([
// Connect werift-sctp client (this resolves once SCTP is connected).
ctx.sctpClient.start(5000),
// This resolves once connected too.
ctx.sctpClient.stateChanged.connected.asPromise(),
// Wait for SCTP state in the mediasoup PlainTransport to be "connected".
new Promise((resolve, reject) => {
if (ctx.plainTransport?.sctpState === 'connected') {
resolve();
}
else {
ctx.plainTransport?.on('sctpstatechange', state => {
if (state === 'connected') {
resolve();
}
else if (state === 'failed' || state === 'closed') {
reject(new Error('SCTP connection in PlainTransport failed or was closed'));
}
});
}
}),
]),
new Promise((resolve, reject) => {
connectionTimeoutTimer = setTimeout(() => reject(new Error('SCTP connection timeout')), 3000);
}),
]);
clearTimeout(connectionTimeoutTimer);
}, 5000);
afterEach(async () => {
await ctx.sctpClient?.stop();
ctx.sctpClient?.transport.close();
ctx.worker?.close();
if (ctx.worker?.subprocessClosed === false) {
await (0, enhancedEvents_1.enhancedOnce)(ctx.worker, 'subprocessclose');
}
});
test('SCTP state is connected', () => {
expect(ctx.plainTransport.sctpState).toBe('connected');
expect(ctx.sctpClient.associationState).toBe(werift_sctp_1.SCTP_STATE.ESTABLISHED);
});
test('ordered DataProducer delivers all SCTP messages to the DataConsumer', async () => {
const numMessages = 200;
let sentMessageBytes = 0;
let recvMessageBytes = 0;
let numSentMessages = 0;
let numReceivedMessages = 0;
// It must be zero because it's the first DataConsumer on the plainTransport.
expect(ctx.dataConsumer.sctpStreamParameters?.streamId).toBe(0);
await new Promise((resolve, reject) => {
sendNextMessage();
function sendNextMessage() {
const id = ++numSentMessages;
const data = Buffer.from(String(id));
let ppid;
// Set ppid of type WebRTC DataChannel string.
if (id < numMessages / 2) {
ppid = werift_sctp_1.WEBRTC_PPID.STRING;
}
// Set ppid of type WebRTC DataChannel binary.
else {
ppid = werift_sctp_1.WEBRTC_PPID.BINARY;
}
void ctx.sctpClient.send(ctx.sctpSendStreamId, ppid, data);
sentMessageBytes += data.byteLength;
if (id < numMessages) {
sendNextMessage();
}
}
ctx.sctpClient.onReceive.subscribe((streamId, ppid, data) => {
// `streamId` must be zero because it's the first SCTP incoming stream
// (so first DataConsumer).
if (streamId !== 0) {
reject(new Error(`streamId should be 0 but it is ${streamId}`));
return;
}
++numReceivedMessages;
recvMessageBytes += data.byteLength;
const id = Number(data.toString('utf8'));
if (id !== numReceivedMessages) {
reject(new Error(`id ${id} in message should match numReceivedMessages ${numReceivedMessages}`));
}
else if (id === numMessages) {
resolve();
}
else if (id < numMessages / 2 && ppid !== werift_sctp_1.WEBRTC_PPID.STRING) {
reject(new Error(`ppid in message with id ${id} should be ${werift_sctp_1.WEBRTC_PPID.STRING} but it is ${ppid}`));
}
else if (id > numMessages / 2 && ppid !== werift_sctp_1.WEBRTC_PPID.BINARY) {
reject(new Error(`ppid in message with id ${id} should be ${werift_sctp_1.WEBRTC_PPID.BINARY} but it is ${ppid}`));
return;
}
});
});
expect(numSentMessages).toBe(numMessages);
expect(numReceivedMessages).toBe(numMessages);
expect(recvMessageBytes).toBe(sentMessageBytes);
await expect(ctx.dataProducer.getStats()).resolves.toMatchObject([
{
type: 'data-producer',
label: ctx.dataProducer.label,
protocol: ctx.dataProducer.protocol,
messagesReceived: numMessages,
bytesReceived: sentMessageBytes,
},
]);
await expect(ctx.dataConsumer.getStats()).resolves.toMatchObject([
{
type: 'data-consumer',
label: ctx.dataConsumer.label,
protocol: ctx.dataConsumer.protocol,
messagesSent: numMessages,
bytesSent: recvMessageBytes,
},
]);
}, 10000);