mediasoup
Version:
Cutting Edge WebRTC Video Conferencing
435 lines (434 loc) • 17.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const os = require("node:os");
const pick_port_1 = require("pick-port");
const mediasoup = require("../");
const enhancedEvents_1 = require("../enhancedEvents");
const utils = require("../utils");
const IS_WINDOWS = os.platform() === 'win32';
const ctx = {
mediaCodecs: utils.deepFreeze([
{
kind: 'audio',
mimeType: 'audio/opus',
clockRate: 48000,
channels: 2,
parameters: {
useinbandfec: 1,
foo: 'bar',
},
},
{
kind: 'video',
mimeType: 'video/VP8',
clockRate: 90000,
},
{
kind: 'video',
mimeType: 'video/H264',
clockRate: 90000,
parameters: {
'level-asymmetry-allowed': 1,
'packetization-mode': 1,
'profile-level-id': '4d0032',
foo: 'bar',
},
rtcpFeedback: [], // Will be ignored.
},
]),
};
beforeEach(async () => {
ctx.worker = await mediasoup.createWorker();
ctx.router = await ctx.worker.createRouter({ mediaCodecs: ctx.mediaCodecs });
});
afterEach(async () => {
ctx.worker?.close();
if (ctx.worker?.subprocessClosed === false) {
await (0, enhancedEvents_1.enhancedOnce)(ctx.worker, 'subprocessclose');
}
});
test('router.createPlainTransport() succeeds', async () => {
const plainTransport = await ctx.router.createPlainTransport({
listenInfo: {
protocol: 'udp',
ip: '127.0.0.1',
portRange: { min: 2000, max: 3000 },
},
});
await expect(ctx.router.dump()).resolves.toMatchObject({
transportIds: [plainTransport.id],
});
const onObserverNewTransport = jest.fn();
ctx.router.observer.once('newtransport', onObserverNewTransport);
// Create a separate transport here.
const plainTransport2 = await ctx.router.createPlainTransport({
listenInfo: {
protocol: 'udp',
ip: '127.0.0.1',
announcedAddress: '9.9.9.1',
portRange: { min: 2000, max: 3000 },
},
enableSctp: true,
appData: { foo: 'bar' },
});
expect(onObserverNewTransport).toHaveBeenCalledTimes(1);
expect(onObserverNewTransport).toHaveBeenCalledWith(plainTransport2);
expect(typeof plainTransport2.id).toBe('string');
expect(plainTransport2.closed).toBe(false);
expect(plainTransport2.appData).toEqual({ foo: 'bar' });
expect(typeof plainTransport2.tuple).toBe('object');
// @deprecated Use tuple.localAddress instead.
expect(plainTransport2.tuple.localIp).toBe('9.9.9.1');
expect(plainTransport2.tuple.localAddress).toBe('9.9.9.1');
expect(typeof plainTransport2.tuple.localPort).toBe('number');
expect(plainTransport2.tuple.protocol).toBe('udp');
expect(plainTransport2.rtcpTuple).toBeUndefined();
expect(plainTransport2.sctpParameters).toMatchObject({
port: 5000,
OS: 1024,
MIS: 1024,
maxMessageSize: 262144,
});
expect(plainTransport2.sctpState).toBe('new');
expect(plainTransport2.srtpParameters).toBeUndefined();
const dump1 = await plainTransport2.dump();
expect(dump1.id).toBe(plainTransport2.id);
expect(dump1.direct).toBe(false);
expect(dump1.producerIds).toEqual([]);
expect(dump1.consumerIds).toEqual([]);
expect(dump1.tuple).toEqual(plainTransport2.tuple);
expect(dump1.rtcpTuple).toEqual(plainTransport2.rtcpTuple);
expect(dump1.sctpParameters).toEqual(plainTransport2.sctpParameters);
expect(dump1.sctpState).toBe('new');
expect(dump1.recvRtpHeaderExtensions).toBeDefined();
expect(typeof dump1.rtpListener).toBe('object');
plainTransport2.close();
expect(plainTransport2.closed).toBe(true);
const anotherTransport = await ctx.router.createPlainTransport({
listenIp: '127.0.0.1',
});
expect(typeof anotherTransport).toBe('object');
const rtpPort = await (0, pick_port_1.pickPort)({
type: 'udp',
ip: '127.0.0.1',
reserveTimeout: 0,
});
const rtcpPort = await (0, pick_port_1.pickPort)({
type: 'udp',
ip: '127.0.0.1',
reserveTimeout: 0,
});
const transport2 = await ctx.router.createPlainTransport({
listenInfo: { protocol: 'udp', ip: '127.0.0.1', port: rtpPort },
rtcpListenInfo: { protocol: 'udp', ip: '127.0.0.1', port: rtcpPort },
});
expect(typeof transport2.id).toBe('string');
expect(transport2.closed).toBe(false);
expect(transport2.appData).toEqual({});
expect(typeof transport2.tuple).toBe('object');
// @deprecated Use tuple.localAddress instead.
expect(transport2.tuple.localIp).toBe('127.0.0.1');
expect(transport2.tuple.localAddress).toBe('127.0.0.1');
expect(transport2.tuple.localPort).toBe(rtpPort);
expect(transport2.tuple.protocol).toBe('udp');
expect(typeof transport2.rtcpTuple).toBe('object');
// @deprecated Use tuple.localAddress instead.
expect(transport2.rtcpTuple?.localIp).toBe('127.0.0.1');
expect(transport2.rtcpTuple?.localAddress).toBe('127.0.0.1');
expect(transport2.rtcpTuple?.localPort).toBe(rtcpPort);
expect(transport2.rtcpTuple?.protocol).toBe('udp');
expect(transport2.sctpParameters).toBeUndefined();
expect(transport2.sctpState).toBeUndefined();
const dump2 = await transport2.dump();
expect(dump2.id).toBe(transport2.id);
expect(dump2.direct).toBe(false);
expect(dump2.tuple).toEqual(transport2.tuple);
expect(dump2.rtcpTuple).toEqual(transport2.rtcpTuple);
expect(dump2.sctpState).toBeUndefined();
}, 2000);
test('router.createPlainTransport() with wrong arguments rejects with TypeError', async () => {
// @ts-ignore
await expect(ctx.router.createPlainTransport({})).rejects.toThrow(TypeError);
await expect(ctx.router.createPlainTransport({
listenInfo: {
protocol: 'udp',
ip: '127.0.0.1',
portRange: { min: 4000, max: 3000 },
},
})).rejects.toThrow(TypeError);
await expect(ctx.router.createPlainTransport({ listenIp: '123' })).rejects.toThrow(TypeError);
await expect(
// @ts-ignore
ctx.router.createPlainTransport({ listenIp: ['127.0.0.1'] })).rejects.toThrow(TypeError);
await expect(ctx.router.createPipeTransport({
listenInfo: { protocol: 'tcp', ip: '127.0.0.1' },
})).rejects.toThrow(TypeError);
await expect(ctx.router.createPlainTransport({
listenInfo: { protocol: 'udp', ip: '127.0.0.1' },
// @ts-ignore
appData: 'NOT-AN-OBJECT',
})).rejects.toThrow(TypeError);
}, 2000);
test('router.createPlainTransport() with enableSrtp succeeds', async () => {
// Use default cryptoSuite: 'AES_CM_128_HMAC_SHA1_80'.
const plainTransport = await ctx.router.createPlainTransport({
listenIp: '127.0.0.1',
enableSrtp: true,
});
expect(typeof plainTransport.id).toBe('string');
expect(typeof plainTransport.srtpParameters).toBe('object');
expect(plainTransport.srtpParameters?.cryptoSuite).toBe('AES_CM_128_HMAC_SHA1_80');
expect(plainTransport.srtpParameters?.keyBase64.length).toBe(40);
// Missing srtpParameters.
await expect(plainTransport.connect({
ip: '127.0.0.2',
port: 9999,
})).rejects.toThrow(TypeError);
// Invalid srtpParameters.
await expect(plainTransport.connect({
ip: '127.0.0.2',
port: 9999,
// @ts-ignore
srtpParameters: 1,
})).rejects.toThrow(TypeError);
// Missing srtpParameters.cryptoSuite.
await expect(plainTransport.connect({
ip: '127.0.0.2',
port: 9999,
// @ts-ignore
srtpParameters: {
keyBase64: 'ZnQ3eWJraDg0d3ZoYzM5cXN1Y2pnaHU5NWxrZTVv',
},
})).rejects.toThrow(TypeError);
// Missing srtpParameters.keyBase64.
await expect(plainTransport.connect({
ip: '127.0.0.2',
port: 9999,
// @ts-ignore
srtpParameters: {
cryptoSuite: 'AES_CM_128_HMAC_SHA1_80',
},
})).rejects.toThrow(TypeError);
// Invalid srtpParameters.cryptoSuite.
await expect(plainTransport.connect({
ip: '127.0.0.2',
port: 9999,
srtpParameters: {
// @ts-ignore
cryptoSuite: 'FOO',
keyBase64: 'ZnQ3eWJraDg0d3ZoYzM5cXN1Y2pnaHU5NWxrZTVv',
},
})).rejects.toThrow(TypeError);
// Invalid srtpParameters.cryptoSuite.
await expect(plainTransport.connect({
ip: '127.0.0.2',
port: 9999,
srtpParameters: {
// @ts-ignore
cryptoSuite: 123,
keyBase64: 'ZnQ3eWJraDg0d3ZoYzM5cXN1Y2pnaHU5NWxrZTVv',
},
})).rejects.toThrow(TypeError);
// Invalid srtpParameters.keyBase64.
await expect(plainTransport.connect({
ip: '127.0.0.2',
port: 9999,
srtpParameters: {
cryptoSuite: 'AES_CM_128_HMAC_SHA1_80',
// @ts-ignore
keyBase64: [],
},
})).rejects.toThrow(TypeError);
// Valid srtpParameters. And let's update the crypto suite.
await expect(plainTransport.connect({
ip: '127.0.0.2',
port: 9999,
srtpParameters: {
cryptoSuite: 'AEAD_AES_256_GCM',
keyBase64: 'YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=',
},
})).resolves.toBeUndefined();
expect(plainTransport.srtpParameters?.cryptoSuite).toBe('AEAD_AES_256_GCM');
expect(plainTransport.srtpParameters?.keyBase64.length).toBe(60);
}, 2000);
test('router.createPlainTransport() with non bindable IP rejects with Error', async () => {
await expect(ctx.router.createPlainTransport({ listenIp: '8.8.8.8' })).rejects.toThrow(Error);
}, 2000);
if (!IS_WINDOWS) {
test('two transports binding to the same IP:port with udpReusePort flag succeed', async () => {
const multicastIp = '224.0.0.1';
const port = await (0, pick_port_1.pickPort)({
type: 'udp',
ip: multicastIp,
reserveTimeout: 0,
});
await expect(ctx.router.createPlainTransport({
listenInfo: {
protocol: 'udp',
ip: multicastIp,
port: port,
// NOTE: ipv6Only flag will be ignored since ip is IPv4.
flags: { udpReusePort: true, ipv6Only: true },
},
})).resolves.toBeDefined();
await expect(ctx.router.createPlainTransport({
listenInfo: {
protocol: 'udp',
ip: multicastIp,
port: port,
flags: { udpReusePort: true },
},
})).resolves.toBeDefined();
}, 2000);
test('two transports binding to the same IP:port without udpReusePort flag fail', async () => {
const multicastIp = '224.0.0.1';
const port = await (0, pick_port_1.pickPort)({
type: 'udp',
ip: multicastIp,
reserveTimeout: 0,
});
await expect(ctx.router.createPlainTransport({
listenInfo: {
protocol: 'udp',
ip: multicastIp,
port: port,
flags: { udpReusePort: false },
},
})).resolves.toBeDefined();
await expect(ctx.router.createPlainTransport({
listenInfo: {
protocol: 'udp',
ip: multicastIp,
port: port,
flags: { udpReusePort: false },
},
})).rejects.toThrow();
}, 2000);
}
test('plainTransport.getStats() succeeds', async () => {
const plainTransport = await ctx.router.createPlainTransport({
listenIp: '127.0.0.1',
});
const stats = await plainTransport.getStats();
expect(Array.isArray(stats)).toBe(true);
expect(stats.length).toBe(1);
expect(stats[0].type).toBe('plain-rtp-transport');
expect(stats[0].transportId).toBe(plainTransport.id);
expect(typeof stats[0].timestamp).toBe('number');
expect(stats[0].bytesReceived).toBe(0);
expect(stats[0].recvBitrate).toBe(0);
expect(stats[0].bytesSent).toBe(0);
expect(stats[0].sendBitrate).toBe(0);
expect(stats[0].rtpBytesReceived).toBe(0);
expect(stats[0].rtpRecvBitrate).toBe(0);
expect(stats[0].rtpBytesSent).toBe(0);
expect(stats[0].rtpSendBitrate).toBe(0);
expect(stats[0].rtxBytesReceived).toBe(0);
expect(stats[0].rtxRecvBitrate).toBe(0);
expect(stats[0].rtxBytesSent).toBe(0);
expect(stats[0].rtxSendBitrate).toBe(0);
expect(stats[0].probationBytesSent).toBe(0);
expect(stats[0].probationSendBitrate).toBe(0);
expect(typeof stats[0].tuple).toBe('object');
// @deprecated Use tuple.localAddress instead.
expect(stats[0].tuple.localIp).toBe('127.0.0.1');
expect(stats[0].tuple.localAddress).toBe('127.0.0.1');
expect(typeof stats[0].tuple.localPort).toBe('number');
expect(stats[0].tuple.protocol).toBe('udp');
expect(stats[0].rtcpTuple).toBeUndefined();
}, 2000);
test('plainTransport.connect() succeeds', async () => {
const plainTransport = await ctx.router.createPlainTransport({
listenIp: '127.0.0.1',
rtcpMux: false,
});
await expect(plainTransport.connect({ ip: '1.2.3.4', port: 1234, rtcpPort: 1235 })).resolves.toBeUndefined();
// Must fail if connected.
await expect(plainTransport.connect({ ip: '1.2.3.4', port: 1234, rtcpPort: 1235 })).rejects.toThrow(Error);
expect(plainTransport.tuple.remoteIp).toBe('1.2.3.4');
expect(plainTransport.tuple.remotePort).toBe(1234);
expect(plainTransport.tuple.protocol).toBe('udp');
expect(plainTransport.rtcpTuple?.remoteIp).toBe('1.2.3.4');
expect(plainTransport.rtcpTuple?.remotePort).toBe(1235);
expect(plainTransport.rtcpTuple?.protocol).toBe('udp');
}, 2000);
test('plainTransport.connect() with wrong arguments rejects with TypeError', async () => {
const plainTransport = await ctx.router.createPlainTransport({
listenIp: '127.0.0.1',
rtcpMux: false,
});
// No SRTP enabled so passing srtpParameters must fail.
await expect(plainTransport.connect({
ip: '127.0.0.2',
port: 9998,
rtcpPort: 9999,
srtpParameters: {
cryptoSuite: 'AES_CM_128_HMAC_SHA1_80',
keyBase64: 'ZnQ3eWJraDg0d3ZoYzM5cXN1Y2pnaHU5NWxrZTVv',
},
})).rejects.toThrow(TypeError);
await expect(plainTransport.connect({})).rejects.toThrow(TypeError);
await expect(plainTransport.connect({ ip: '::::1234' })).rejects.toThrow(TypeError);
// Must fail because transport has rtcpMux: false so rtcpPort must be given
// in connect().
await expect(plainTransport.connect({
ip: '127.0.0.1',
port: 1234,
// @ts-ignore
__rtcpPort: 1235,
})).rejects.toThrow(TypeError);
await expect(plainTransport.connect({
ip: '127.0.0.1',
// @ts-ignore
__port: 'chicken',
rtcpPort: 1235,
})).rejects.toThrow(TypeError);
}, 2000);
test('PlainTransport methods reject if closed', async () => {
const plainTransport = await ctx.router.createPlainTransport({
listenIp: '127.0.0.1',
});
const onObserverClose = jest.fn();
plainTransport.observer.once('close', onObserverClose);
plainTransport.close();
expect(onObserverClose).toHaveBeenCalledTimes(1);
expect(plainTransport.closed).toBe(true);
await expect(plainTransport.dump()).rejects.toThrow(Error);
await expect(plainTransport.getStats()).rejects.toThrow(Error);
await expect(plainTransport.connect({})).rejects.toThrow(Error);
}, 2000);
test('router.createPlainTransport() with fixed port succeeds', async () => {
const port = await (0, pick_port_1.pickPort)({
type: 'udp',
ip: '127.0.0.1',
reserveTimeout: 0,
});
const plainTransport = await ctx.router.createPlainTransport({
listenInfo: { protocol: 'udp', ip: '127.0.0.1', port },
});
expect(plainTransport.tuple.localPort).toEqual(port);
}, 2000);
test('PlainTransport emits "routerclose" if Router is closed', async () => {
const plainTransport = await ctx.router.createPlainTransport({
listenIp: '127.0.0.1',
});
const onObserverClose = jest.fn();
plainTransport.observer.once('close', onObserverClose);
const promise = (0, enhancedEvents_1.enhancedOnce)(plainTransport, 'routerclose');
ctx.router.close();
await promise;
expect(onObserverClose).toHaveBeenCalledTimes(1);
expect(plainTransport.closed).toBe(true);
}, 2000);
test('PlainTransport emits "routerclose" if Worker is closed', async () => {
const plainTransport = await ctx.router.createPlainTransport({
listenIp: '127.0.0.1',
});
const onObserverClose = jest.fn();
plainTransport.observer.once('close', onObserverClose);
const promise = (0, enhancedEvents_1.enhancedOnce)(plainTransport, 'routerclose');
ctx.worker.close();
await promise;
expect(onObserverClose).toHaveBeenCalledTimes(1);
expect(plainTransport.closed).toBe(true);
}, 2000);