UNPKG

mediasoup

Version:

Cutting Edge WebRTC Video Conferencing

435 lines (434 loc) 17.3 kB
"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);