mediasoup-client 3.6.90 → 3.6.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/lib/Consumer.d.ts +115 -0
  2. package/lib/Consumer.d.ts.map +1 -0
  3. package/lib/Consumer.js +190 -0
  4. package/lib/DataConsumer.d.ts +93 -0
  5. package/lib/DataConsumer.d.ts.map +1 -0
  6. package/lib/DataConsumer.js +162 -0
  7. package/lib/DataProducer.d.ts +97 -0
  8. package/lib/DataProducer.d.ts.map +1 -0
  9. package/lib/DataProducer.js +180 -0
  10. package/lib/Device.d.ts +92 -0
  11. package/lib/Device.d.ts.map +1 -0
  12. package/lib/Device.js +463 -0
  13. package/lib/EnhancedEventEmitter.d.ts +24 -0
  14. package/lib/EnhancedEventEmitter.d.ts.map +1 -0
  15. package/lib/EnhancedEventEmitter.js +70 -0
  16. package/lib/Logger.d.ts +11 -0
  17. package/lib/Logger.d.ts.map +1 -0
  18. package/lib/Logger.js +37 -0
  19. package/lib/Producer.d.ts +169 -0
  20. package/lib/Producer.d.ts.map +1 -0
  21. package/lib/Producer.js +287 -0
  22. package/lib/RtpParameters.d.ts +306 -0
  23. package/lib/RtpParameters.d.ts.map +1 -0
  24. package/lib/RtpParameters.js +6 -0
  25. package/lib/SctpParameters.d.ts +68 -0
  26. package/lib/SctpParameters.d.ts.map +1 -0
  27. package/lib/SctpParameters.js +2 -0
  28. package/lib/Transport.d.ts +243 -0
  29. package/lib/Transport.d.ts.map +1 -0
  30. package/lib/Transport.js +824 -0
  31. package/lib/errors.d.ts +13 -0
  32. package/lib/errors.d.ts.map +1 -0
  33. package/lib/errors.js +39 -0
  34. package/lib/handlers/Chrome111.d.ts +49 -0
  35. package/lib/handlers/Chrome111.d.ts.map +1 -0
  36. package/lib/handlers/Chrome111.js +654 -0
  37. package/lib/handlers/Chrome55.d.ts +51 -0
  38. package/lib/handlers/Chrome55.d.ts.map +1 -0
  39. package/lib/handlers/Chrome55.js +538 -0
  40. package/lib/handlers/Chrome67.d.ts +51 -0
  41. package/lib/handlers/Chrome67.d.ts.map +1 -0
  42. package/lib/handlers/Chrome67.js +594 -0
  43. package/lib/handlers/Chrome70.d.ts +49 -0
  44. package/lib/handlers/Chrome70.d.ts.map +1 -0
  45. package/lib/handlers/Chrome70.js +623 -0
  46. package/lib/handlers/Chrome74.d.ts +49 -0
  47. package/lib/handlers/Chrome74.d.ts.map +1 -0
  48. package/lib/handlers/Chrome74.js +671 -0
  49. package/lib/handlers/Edge11.d.ts +55 -0
  50. package/lib/handlers/Edge11.d.ts.map +1 -0
  51. package/lib/handlers/Edge11.js +447 -0
  52. package/lib/handlers/FakeHandler.d.ts +49 -0
  53. package/lib/handlers/FakeHandler.d.ts.map +1 -0
  54. package/lib/handlers/FakeHandler.js +276 -0
  55. package/lib/handlers/Firefox60.d.ts +48 -0
  56. package/lib/handlers/Firefox60.d.ts.map +1 -0
  57. package/lib/handlers/Firefox60.js +681 -0
  58. package/lib/handlers/HandlerInterface.d.ts +97 -0
  59. package/lib/handlers/HandlerInterface.d.ts.map +1 -0
  60. package/lib/handlers/HandlerInterface.js +10 -0
  61. package/lib/handlers/ReactNative.d.ts +51 -0
  62. package/lib/handlers/ReactNative.d.ts.map +1 -0
  63. package/lib/handlers/ReactNative.js +554 -0
  64. package/lib/handlers/ReactNativeUnifiedPlan.d.ts +49 -0
  65. package/lib/handlers/ReactNativeUnifiedPlan.d.ts.map +1 -0
  66. package/lib/handlers/ReactNativeUnifiedPlan.js +690 -0
  67. package/lib/handlers/Safari11.d.ts +51 -0
  68. package/lib/handlers/Safari11.d.ts.map +1 -0
  69. package/lib/handlers/Safari11.js +588 -0
  70. package/lib/handlers/Safari12.d.ts +49 -0
  71. package/lib/handlers/Safari12.d.ts.map +1 -0
  72. package/lib/handlers/Safari12.js +644 -0
  73. package/lib/handlers/ortc/edgeUtils.d.ts +11 -0
  74. package/lib/handlers/ortc/edgeUtils.d.ts.map +1 -0
  75. package/lib/handlers/ortc/edgeUtils.js +86 -0
  76. package/lib/handlers/ortc/utils.d.ts +6 -0
  77. package/lib/handlers/ortc/utils.d.ts.map +1 -0
  78. package/lib/handlers/ortc/utils.js +19 -0
  79. package/lib/handlers/sdp/MediaSection.d.ts +68 -0
  80. package/lib/handlers/sdp/MediaSection.d.ts.map +1 -0
  81. package/lib/handlers/sdp/MediaSection.js +588 -0
  82. package/lib/handlers/sdp/RemoteSdp.d.ts +75 -0
  83. package/lib/handlers/sdp/RemoteSdp.d.ts.map +1 -0
  84. package/lib/handlers/sdp/RemoteSdp.js +303 -0
  85. package/lib/handlers/sdp/commonUtils.d.ts +24 -0
  86. package/lib/handlers/sdp/commonUtils.d.ts.map +1 -0
  87. package/lib/handlers/sdp/commonUtils.js +224 -0
  88. package/lib/handlers/sdp/plainRtpUtils.d.ts +14 -0
  89. package/lib/handlers/sdp/plainRtpUtils.d.ts.map +1 -0
  90. package/lib/handlers/sdp/plainRtpUtils.js +33 -0
  91. package/lib/handlers/sdp/planBUtils.d.ts +14 -0
  92. package/lib/handlers/sdp/planBUtils.d.ts.map +1 -0
  93. package/lib/handlers/sdp/planBUtils.js +155 -0
  94. package/lib/handlers/sdp/unifiedPlanUtils.d.ts +12 -0
  95. package/lib/handlers/sdp/unifiedPlanUtils.d.ts.map +1 -0
  96. package/lib/handlers/sdp/unifiedPlanUtils.js +131 -0
  97. package/lib/index.d.ts +14 -1435
  98. package/lib/index.d.ts.map +1 -0
  99. package/lib/index.js +43 -11884
  100. package/lib/ortc.d.ts +123 -0
  101. package/lib/ortc.d.ts.map +1 -0
  102. package/lib/ortc.js +943 -0
  103. package/lib/scalabilityModes.d.ts +6 -0
  104. package/lib/scalabilityModes.d.ts.map +1 -0
  105. package/lib/scalabilityModes.js +20 -0
  106. package/lib/tests/fakeParameters.d.ts +20 -0
  107. package/lib/tests/fakeParameters.d.ts.map +1 -0
  108. package/lib/tests/fakeParameters.js +659 -0
  109. package/lib/tests/test.d.ts +6 -0
  110. package/lib/tests/test.d.ts.map +1 -0
  111. package/lib/tests/test.js +1287 -0
  112. package/lib/tests/uaTestCases.d.ts +9 -0
  113. package/lib/tests/uaTestCases.d.ts.map +1 -0
  114. package/lib/tests/uaTestCases.js +85 -0
  115. package/lib/types.d.ts +15 -0
  116. package/lib/types.d.ts.map +1 -0
  117. package/lib/types.js +26 -0
  118. package/lib/utils.d.ts +9 -0
  119. package/lib/utils.d.ts.map +1 -0
  120. package/lib/utils.js +20 -0
  121. package/npm-scripts.mjs +2 -3
  122. package/package.json +1 -6
  123. package/lib/index.js.map +0 -1
  124. package/lib/index.mjs +0 -11860
  125. package/lib/index.mjs.map +0 -1
@@ -0,0 +1,1287 @@
1
+ "use strict";
2
+ /**
3
+ * This test runs in Node so no browser auto-detection is done. Instead, a
4
+ * FakeHandler device is used.
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || function (mod) {
23
+ if (mod && mod.__esModule) return mod;
24
+ var result = {};
25
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
26
+ __setModuleDefault(result, mod);
27
+ return result;
28
+ };
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ const sdpTransform = __importStar(require("sdp-transform"));
31
+ const fake_mediastreamtrack_1 = require("fake-mediastreamtrack");
32
+ const mediasoupClient = __importStar(require("../"));
33
+ const errors_1 = require("../errors");
34
+ const utils = __importStar(require("../utils"));
35
+ const RemoteSdp_1 = require("../handlers/sdp/RemoteSdp");
36
+ const FakeHandler_1 = require("../handlers/FakeHandler");
37
+ const fakeParameters = __importStar(require("./fakeParameters"));
38
+ const uaTestCases_1 = require("./uaTestCases");
39
+ const { Device, detectDevice, parseScalabilityMode, debug } = mediasoupClient;
40
+ let device;
41
+ let sendTransport;
42
+ let recvTransport;
43
+ let audioProducer;
44
+ let videoProducer;
45
+ let audioConsumer;
46
+ let videoConsumer;
47
+ let dataProducer;
48
+ let dataConsumer;
49
+ test('mediasoup-client exposes debug dependency', () => {
50
+ expect(typeof debug).toBe('function');
51
+ }, 500);
52
+ test('detectDevice() returns nothing in Node', () => {
53
+ expect(detectDevice()).toBe(undefined);
54
+ });
55
+ test('create a Device in Node without custom handlerName/handlerFactory throws UnsupportedError', () => {
56
+ expect(() => new Device())
57
+ .toThrow(errors_1.UnsupportedError);
58
+ });
59
+ test('create a Device with an unknown handlerName string throws TypeError', () => {
60
+ // @ts-ignore
61
+ expect(() => new Device({ handlerName: 'FooBrowser666' }))
62
+ .toThrow(TypeError);
63
+ });
64
+ test('create a Device in Node with a valid handlerFactory succeeds', () => {
65
+ device = new Device({ handlerFactory: FakeHandler_1.FakeHandler.createFactory(fakeParameters) });
66
+ expect(typeof device).toBe('object');
67
+ expect(device.handlerName).toBe('FakeHandler');
68
+ expect(device.loaded).toBe(false);
69
+ });
70
+ test('device.rtpCapabilities getter throws InvalidStateError if not loaded', () => {
71
+ expect(() => device.rtpCapabilities)
72
+ .toThrow(errors_1.InvalidStateError);
73
+ });
74
+ test('device.sctpCapabilities getter throws InvalidStateError if not loaded', () => {
75
+ expect(() => device.sctpCapabilities)
76
+ .toThrow(errors_1.InvalidStateError);
77
+ });
78
+ test('device.canProduce() throws InvalidStateError if not loaded', () => {
79
+ expect(() => device.canProduce('audio'))
80
+ .toThrow(errors_1.InvalidStateError);
81
+ });
82
+ test('device.createSendTransport() throws InvalidStateError if not loaded', () => {
83
+ const { id, iceParameters, iceCandidates, dtlsParameters, sctpParameters } = fakeParameters.generateTransportRemoteParameters();
84
+ expect(() => device.createSendTransport({
85
+ id,
86
+ iceParameters,
87
+ iceCandidates,
88
+ dtlsParameters,
89
+ sctpParameters
90
+ }))
91
+ .toThrow(errors_1.InvalidStateError);
92
+ });
93
+ test('device.load() without routerRtpCapabilities rejects with TypeError', async () => {
94
+ // @ts-ignore
95
+ await expect(device.load({}))
96
+ .rejects
97
+ .toThrow(TypeError);
98
+ expect(device.loaded).toBe(false);
99
+ }, 500);
100
+ test('device.load() with invalid routerRtpCapabilities rejects with TypeError', async () => {
101
+ // Clonse fake router RTP capabilities to make them invalid.
102
+ const routerRtpCapabilities = utils.clone(fakeParameters.generateRouterRtpCapabilities(), {});
103
+ for (const codec of routerRtpCapabilities.codecs) {
104
+ delete codec.mimeType;
105
+ }
106
+ await expect(device.load({ routerRtpCapabilities }))
107
+ .rejects
108
+ .toThrow(TypeError);
109
+ expect(device.loaded).toBe(false);
110
+ }, 500);
111
+ test('device.load() succeeds', async () => {
112
+ // Assume we get the router RTP capabilities.
113
+ const routerRtpCapabilities = fakeParameters.generateRouterRtpCapabilities();
114
+ await expect(device.load({ routerRtpCapabilities }))
115
+ .resolves
116
+ .toBe(undefined);
117
+ expect(device.loaded).toBe(true);
118
+ }, 500);
119
+ test('device.load() rejects with InvalidStateError if already loaded', async () => {
120
+ // @ts-ignore
121
+ await expect(device.load({}))
122
+ .rejects
123
+ .toThrow(errors_1.InvalidStateError);
124
+ expect(device.loaded).toBe(true);
125
+ }, 500);
126
+ test('device.rtpCapabilities getter succeeds', () => {
127
+ expect(typeof device.rtpCapabilities).toBe('object');
128
+ });
129
+ test('device.sctpCapabilities getter succeeds', () => {
130
+ expect(typeof device.sctpCapabilities).toBe('object');
131
+ });
132
+ test('device.canProduce() with "audio"/"video" kind returns true', () => {
133
+ expect(device.canProduce('audio')).toBe(true);
134
+ expect(device.canProduce('video')).toBe(true);
135
+ });
136
+ test('device.canProduce() with invalid kind throws TypeError', () => {
137
+ // @ts-ignore
138
+ expect(() => device.canProduce('chicken'))
139
+ .toThrow(TypeError);
140
+ });
141
+ test('device.createSendTransport() for sending media succeeds', () => {
142
+ // Assume we create a transport in the server and get its remote parameters.
143
+ const { id, iceParameters, iceCandidates, dtlsParameters, sctpParameters } = fakeParameters.generateTransportRemoteParameters();
144
+ sendTransport = device.createSendTransport({
145
+ id,
146
+ iceParameters,
147
+ iceCandidates,
148
+ dtlsParameters,
149
+ sctpParameters,
150
+ appData: { foo: 123 }
151
+ });
152
+ expect(typeof sendTransport).toBe('object');
153
+ expect(sendTransport.id).toBe(id);
154
+ expect(sendTransport.closed).toBe(false);
155
+ expect(sendTransport.direction).toBe('send');
156
+ expect(typeof sendTransport.handler).toBe('object');
157
+ expect(sendTransport.handler instanceof FakeHandler_1.FakeHandler).toBe(true);
158
+ expect(sendTransport.connectionState).toBe('new');
159
+ expect(sendTransport.appData).toEqual({ foo: 123 });
160
+ });
161
+ test('device.createRecvTransport() for receiving media succeeds', () => {
162
+ // Assume we create a transport in the server and get its remote parameters.
163
+ const { id, iceParameters, iceCandidates, dtlsParameters, sctpParameters } = fakeParameters.generateTransportRemoteParameters();
164
+ recvTransport = device.createRecvTransport({
165
+ id,
166
+ iceParameters,
167
+ iceCandidates,
168
+ dtlsParameters,
169
+ sctpParameters
170
+ });
171
+ expect(typeof recvTransport).toBe('object');
172
+ expect(recvTransport.id).toBe(id);
173
+ expect(recvTransport.closed).toBe(false);
174
+ expect(recvTransport.direction).toBe('recv');
175
+ expect(typeof recvTransport.handler).toBe('object');
176
+ expect(recvTransport.handler instanceof FakeHandler_1.FakeHandler).toBe(true);
177
+ expect(recvTransport.connectionState).toBe('new');
178
+ expect(recvTransport.appData).toEqual({});
179
+ });
180
+ test('device.createSendTransport() with missing remote Transport parameters throws TypeError', () => {
181
+ // @ts-ignore
182
+ expect(() => device.createSendTransport({ id: '1234' }))
183
+ .toThrow(TypeError);
184
+ // @ts-ignore
185
+ expect(() => device.createSendTransport({ id: '1234', iceParameters: {} }))
186
+ .toThrow(TypeError);
187
+ expect(() => device.createSendTransport({
188
+ id: '1234',
189
+ // @ts-ignore
190
+ iceParameters: {},
191
+ iceCandidates: []
192
+ }))
193
+ .toThrow(TypeError);
194
+ });
195
+ test('device.createRecvTransport() with a non object appData throws TypeError', () => {
196
+ const { id, iceParameters, iceCandidates, dtlsParameters, sctpParameters } = fakeParameters.generateTransportRemoteParameters();
197
+ expect(() => device.createRecvTransport({
198
+ id,
199
+ iceParameters,
200
+ iceCandidates,
201
+ dtlsParameters,
202
+ sctpParameters,
203
+ // @ts-ignore
204
+ appData: 1234
205
+ }))
206
+ .toThrow(TypeError);
207
+ });
208
+ test('transport.produce() without "connect" listener rejects', async () => {
209
+ const audioTrack = new fake_mediastreamtrack_1.FakeMediaStreamTrack({ kind: 'audio' });
210
+ await expect(sendTransport.produce({ track: audioTrack }))
211
+ .rejects
212
+ .toThrow(Error);
213
+ }, 500);
214
+ test('transport.produce() succeeds', async () => {
215
+ const audioTrack = new fake_mediastreamtrack_1.FakeMediaStreamTrack({ kind: 'audio' });
216
+ const videoTrack = new fake_mediastreamtrack_1.FakeMediaStreamTrack({ kind: 'video' });
217
+ let audioProducerId;
218
+ let videoProducerId;
219
+ let connectEventNumTimesCalled = 0;
220
+ let produceEventNumTimesCalled = 0;
221
+ // eslint-disable-next-line no-unused-vars
222
+ sendTransport.on('connect', ({ dtlsParameters }, callback /* errback */) => {
223
+ connectEventNumTimesCalled++;
224
+ expect(typeof dtlsParameters).toBe('object');
225
+ // Emulate communication with the server and success response (no response
226
+ // data needed).
227
+ setTimeout(callback);
228
+ });
229
+ // eslint-disable-next-line no-unused-vars
230
+ sendTransport.on('produce', ({ kind, rtpParameters, appData }, callback /* errback */) => {
231
+ produceEventNumTimesCalled++;
232
+ expect(typeof kind).toBe('string');
233
+ expect(typeof rtpParameters).toBe('object');
234
+ let id;
235
+ switch (kind) {
236
+ case 'audio':
237
+ {
238
+ expect(appData).toEqual({ foo: 'FOO' });
239
+ id = fakeParameters.generateProducerRemoteParameters().id;
240
+ audioProducerId = id;
241
+ break;
242
+ }
243
+ case 'video':
244
+ {
245
+ expect(appData).toEqual({});
246
+ id = fakeParameters.generateProducerRemoteParameters().id;
247
+ videoProducerId = id;
248
+ break;
249
+ }
250
+ default:
251
+ {
252
+ throw new Error('unknown kind');
253
+ }
254
+ }
255
+ // Emulate communication with the server and success response with Producer
256
+ // remote parameters.
257
+ setTimeout(() => callback({ id }));
258
+ });
259
+ let codecs;
260
+ let headerExtensions;
261
+ let encodings;
262
+ let rtcp;
263
+ // Pause the audio track before creating its Producer.
264
+ audioTrack.enabled = false;
265
+ // Use stopTracks: false.
266
+ audioProducer = await sendTransport.produce({ track: audioTrack, stopTracks: false, appData: { foo: 'FOO' } });
267
+ expect(connectEventNumTimesCalled).toBe(1);
268
+ expect(produceEventNumTimesCalled).toBe(1);
269
+ expect(typeof audioProducer).toBe('object');
270
+ expect(audioProducer.id).toBe(audioProducerId);
271
+ expect(audioProducer.closed).toBe(false);
272
+ expect(audioProducer.kind).toBe('audio');
273
+ expect(audioProducer.track).toBe(audioTrack);
274
+ expect(typeof audioProducer.rtpParameters).toBe('object');
275
+ expect(typeof audioProducer.rtpParameters.mid).toBe('string');
276
+ expect(audioProducer.rtpParameters.codecs.length).toBe(1);
277
+ codecs = audioProducer.rtpParameters.codecs;
278
+ expect(codecs[0]).toEqual({
279
+ mimeType: 'audio/opus',
280
+ payloadType: 111,
281
+ clockRate: 48000,
282
+ channels: 2,
283
+ rtcpFeedback: [
284
+ { type: 'transport-cc', parameter: '' }
285
+ ],
286
+ parameters: {
287
+ minptime: 10,
288
+ useinbandfec: 1
289
+ }
290
+ });
291
+ headerExtensions = audioProducer.rtpParameters.headerExtensions;
292
+ expect(headerExtensions).toEqual([
293
+ {
294
+ uri: 'urn:ietf:params:rtp-hdrext:sdes:mid',
295
+ id: 1,
296
+ encrypt: false,
297
+ parameters: {}
298
+ },
299
+ {
300
+ uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',
301
+ id: 10,
302
+ encrypt: false,
303
+ parameters: {}
304
+ }
305
+ ]);
306
+ encodings = audioProducer.rtpParameters.encodings;
307
+ expect(Array.isArray(encodings)).toBe(true);
308
+ expect(encodings.length).toBe(1);
309
+ expect(typeof encodings?.[0]).toBe('object');
310
+ expect(Object.keys(encodings[0])).toEqual(['ssrc', 'dtx']);
311
+ expect(typeof encodings?.[0].ssrc).toBe('number');
312
+ rtcp = audioProducer.rtpParameters.rtcp;
313
+ expect(typeof rtcp).toBe('object');
314
+ expect(typeof rtcp?.cname).toBe('string');
315
+ expect(audioProducer.paused).toBe(true);
316
+ expect(audioProducer.maxSpatialLayer).toBe(undefined);
317
+ expect(audioProducer.appData).toEqual({ foo: 'FOO' });
318
+ // Reset the audio paused state.
319
+ audioProducer.resume();
320
+ const videoEncodings = [
321
+ { maxBitrate: 100000 },
322
+ { maxBitrate: 500000 }
323
+ ];
324
+ // Note that stopTracks is not give so it's true by default.
325
+ // Use disableTrackOnPause: false and zeroRtpOnPause: true
326
+ videoProducer = await sendTransport.produce({
327
+ track: videoTrack,
328
+ encodings: videoEncodings,
329
+ disableTrackOnPause: false,
330
+ zeroRtpOnPause: true
331
+ });
332
+ expect(connectEventNumTimesCalled).toBe(1);
333
+ expect(produceEventNumTimesCalled).toBe(2);
334
+ expect(typeof videoProducer).toBe('object');
335
+ expect(videoProducer.id).toBe(videoProducerId);
336
+ expect(videoProducer.closed).toBe(false);
337
+ expect(videoProducer.kind).toBe('video');
338
+ expect(videoProducer.track).toBe(videoTrack);
339
+ expect(typeof videoProducer.rtpParameters).toBe('object');
340
+ expect(typeof videoProducer.rtpParameters.mid).toBe('string');
341
+ expect(videoProducer.rtpParameters.codecs.length).toBe(2);
342
+ codecs = videoProducer.rtpParameters.codecs;
343
+ expect(codecs[0]).toEqual({
344
+ mimeType: 'video/VP8',
345
+ payloadType: 96,
346
+ clockRate: 90000,
347
+ rtcpFeedback: [
348
+ { type: 'goog-remb', parameter: '' },
349
+ { type: 'transport-cc', parameter: '' },
350
+ { type: 'ccm', parameter: 'fir' },
351
+ { type: 'nack', parameter: '' },
352
+ { type: 'nack', parameter: 'pli' }
353
+ ],
354
+ parameters: {
355
+ baz: '1234abcd'
356
+ }
357
+ });
358
+ expect(codecs[1]).toEqual({
359
+ mimeType: 'video/rtx',
360
+ payloadType: 97,
361
+ clockRate: 90000,
362
+ rtcpFeedback: [],
363
+ parameters: {
364
+ apt: 96
365
+ }
366
+ });
367
+ headerExtensions = videoProducer.rtpParameters.headerExtensions;
368
+ expect(headerExtensions).toEqual([
369
+ {
370
+ uri: 'urn:ietf:params:rtp-hdrext:sdes:mid',
371
+ id: 1,
372
+ encrypt: false,
373
+ parameters: {}
374
+ },
375
+ {
376
+ uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',
377
+ id: 3,
378
+ encrypt: false,
379
+ parameters: {}
380
+ },
381
+ {
382
+ uri: 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01',
383
+ id: 5,
384
+ encrypt: false,
385
+ parameters: {}
386
+ },
387
+ {
388
+ uri: 'urn:3gpp:video-orientation',
389
+ id: 4,
390
+ encrypt: false,
391
+ parameters: {}
392
+ },
393
+ {
394
+ uri: 'urn:ietf:params:rtp-hdrext:toffset',
395
+ id: 2,
396
+ encrypt: false,
397
+ parameters: {}
398
+ }
399
+ ]);
400
+ encodings = videoProducer.rtpParameters.encodings;
401
+ expect(Array.isArray(encodings)).toBe(true);
402
+ expect(encodings.length).toBe(2);
403
+ expect(typeof encodings?.[0]).toBe('object');
404
+ expect(typeof encodings?.[0].ssrc).toBe('number');
405
+ expect(typeof encodings?.[0].rtx).toBe('object');
406
+ expect(Object.keys(encodings[0].rtx)).toEqual(['ssrc']);
407
+ expect(typeof encodings?.[0].rtx?.ssrc).toBe('number');
408
+ expect(typeof encodings?.[1]).toBe('object');
409
+ expect(typeof encodings?.[1].ssrc).toBe('number');
410
+ expect(typeof encodings?.[1].rtx).toBe('object');
411
+ expect(Object.keys(encodings[1].rtx)).toEqual(['ssrc']);
412
+ expect(typeof encodings?.[1].rtx?.ssrc).toBe('number');
413
+ rtcp = videoProducer.rtpParameters.rtcp;
414
+ expect(typeof rtcp).toBe('object');
415
+ expect(typeof rtcp?.cname).toBe('string');
416
+ expect(videoProducer.paused).toBe(false);
417
+ expect(videoProducer.maxSpatialLayer).toBe(undefined);
418
+ expect(videoProducer.appData).toEqual({});
419
+ sendTransport.removeAllListeners('connect');
420
+ sendTransport.removeAllListeners('produce');
421
+ }, 500);
422
+ test('transport.produce() without track rejects with TypeError', async () => {
423
+ await expect(sendTransport.produce({}))
424
+ .rejects
425
+ .toThrow(TypeError);
426
+ }, 500);
427
+ test('transport.produce() in a receiving Transport rejects with UnsupportedError', async () => {
428
+ const track = new fake_mediastreamtrack_1.FakeMediaStreamTrack({ kind: 'audio' });
429
+ await expect(recvTransport.produce({ track }))
430
+ .rejects
431
+ .toThrow(errors_1.UnsupportedError);
432
+ }, 500);
433
+ test('transport.produce() with an ended track rejects with InvalidStateError', async () => {
434
+ const track = new fake_mediastreamtrack_1.FakeMediaStreamTrack({ kind: 'audio' });
435
+ track.stop();
436
+ await expect(sendTransport.produce({ track }))
437
+ .rejects
438
+ .toThrow(errors_1.InvalidStateError);
439
+ }, 500);
440
+ test('transport.produce() with a non object appData rejects with TypeError', async () => {
441
+ const track = new fake_mediastreamtrack_1.FakeMediaStreamTrack({ kind: 'audio' });
442
+ // @ts-ignore
443
+ await expect(sendTransport.produce({ track, appData: true }))
444
+ .rejects
445
+ .toThrow(TypeError);
446
+ }, 500);
447
+ test('transport.consume() succeeds', async () => {
448
+ const audioConsumerRemoteParameters = fakeParameters.generateConsumerRemoteParameters({ codecMimeType: 'audio/opus' });
449
+ const videoConsumerRemoteParameters = fakeParameters.generateConsumerRemoteParameters({ codecMimeType: 'video/VP8' });
450
+ let connectEventNumTimesCalled = 0;
451
+ // eslint-disable-next-line no-unused-vars
452
+ recvTransport.on('connect', ({ dtlsParameters }, callback /* errback */) => {
453
+ connectEventNumTimesCalled++;
454
+ expect(typeof dtlsParameters).toBe('object');
455
+ // Emulate communication with the server and success response (no response
456
+ // data needed).
457
+ setTimeout(callback);
458
+ });
459
+ let codecs;
460
+ let headerExtensions;
461
+ let encodings;
462
+ let rtcp;
463
+ audioConsumer = await recvTransport.consume({
464
+ id: audioConsumerRemoteParameters.id,
465
+ producerId: audioConsumerRemoteParameters.producerId,
466
+ kind: audioConsumerRemoteParameters.kind,
467
+ rtpParameters: audioConsumerRemoteParameters.rtpParameters,
468
+ appData: { bar: 'BAR' }
469
+ });
470
+ expect(connectEventNumTimesCalled).toBe(1);
471
+ expect(typeof audioConsumer).toBe('object');
472
+ expect(audioConsumer.id).toBe(audioConsumerRemoteParameters.id);
473
+ expect(audioConsumer.producerId).toBe(audioConsumerRemoteParameters.producerId);
474
+ expect(audioConsumer.closed).toBe(false);
475
+ expect(audioConsumer.kind).toBe('audio');
476
+ expect(typeof audioConsumer.track).toBe('object');
477
+ expect(typeof audioConsumer.rtpParameters).toBe('object');
478
+ expect(audioConsumer.rtpParameters.mid).toBe(undefined);
479
+ expect(audioConsumer.rtpParameters.codecs.length).toBe(1);
480
+ codecs = audioConsumer.rtpParameters.codecs;
481
+ expect(codecs[0]).toEqual({
482
+ mimeType: 'audio/opus',
483
+ payloadType: 100,
484
+ clockRate: 48000,
485
+ channels: 2,
486
+ rtcpFeedback: [
487
+ { type: 'transport-cc', parameter: '' }
488
+ ],
489
+ parameters: {
490
+ useinbandfec: 1,
491
+ foo: 'bar'
492
+ }
493
+ });
494
+ headerExtensions = audioConsumer.rtpParameters.headerExtensions;
495
+ expect(headerExtensions).toEqual([
496
+ {
497
+ uri: 'urn:ietf:params:rtp-hdrext:sdes:mid',
498
+ id: 1,
499
+ encrypt: false,
500
+ parameters: {}
501
+ },
502
+ {
503
+ uri: 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01',
504
+ id: 5,
505
+ encrypt: false,
506
+ parameters: {}
507
+ },
508
+ {
509
+ uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',
510
+ id: 10,
511
+ encrypt: false,
512
+ parameters: {}
513
+ }
514
+ ]);
515
+ encodings = audioConsumer.rtpParameters.encodings;
516
+ expect(Array.isArray(encodings)).toBe(true);
517
+ expect(encodings.length).toBe(1);
518
+ expect(typeof encodings?.[0]).toBe('object');
519
+ expect(Object.keys(encodings[0])).toEqual(['ssrc', 'dtx']);
520
+ expect(typeof encodings[0].ssrc).toBe('number');
521
+ rtcp = audioProducer.rtpParameters.rtcp;
522
+ expect(typeof rtcp).toBe('object');
523
+ expect(typeof rtcp?.cname).toBe('string');
524
+ expect(audioConsumer.paused).toBe(false);
525
+ expect(audioConsumer.appData).toEqual({ bar: 'BAR' });
526
+ videoConsumer = await recvTransport.consume({
527
+ id: videoConsumerRemoteParameters.id,
528
+ producerId: videoConsumerRemoteParameters.producerId,
529
+ kind: videoConsumerRemoteParameters.kind,
530
+ rtpParameters: videoConsumerRemoteParameters.rtpParameters
531
+ });
532
+ expect(connectEventNumTimesCalled).toBe(1);
533
+ expect(typeof videoConsumer).toBe('object');
534
+ expect(videoConsumer.id).toBe(videoConsumerRemoteParameters.id);
535
+ expect(videoConsumer.producerId).toBe(videoConsumerRemoteParameters.producerId);
536
+ expect(videoConsumer.closed).toBe(false);
537
+ expect(videoConsumer.kind).toBe('video');
538
+ expect(typeof videoConsumer.track).toBe('object');
539
+ expect(typeof videoConsumer.rtpParameters).toBe('object');
540
+ expect(videoConsumer.rtpParameters.mid).toBe(undefined);
541
+ expect(videoConsumer.rtpParameters.codecs.length).toBe(2);
542
+ codecs = videoConsumer.rtpParameters.codecs;
543
+ expect(codecs[0]).toEqual({
544
+ mimeType: 'video/VP8',
545
+ payloadType: 101,
546
+ clockRate: 90000,
547
+ rtcpFeedback: [
548
+ { type: 'nack', parameter: '' },
549
+ { type: 'nack', parameter: 'pli' },
550
+ { type: 'ccm', parameter: 'fir' },
551
+ { type: 'goog-remb', parameter: '' },
552
+ { type: 'transport-cc', parameter: '' }
553
+ ],
554
+ parameters: {
555
+ 'x-google-start-bitrate': 1500
556
+ }
557
+ });
558
+ expect(codecs[1]).toEqual({
559
+ mimeType: 'video/rtx',
560
+ payloadType: 102,
561
+ clockRate: 90000,
562
+ rtcpFeedback: [],
563
+ parameters: {
564
+ apt: 101
565
+ }
566
+ });
567
+ headerExtensions = videoConsumer.rtpParameters.headerExtensions;
568
+ expect(headerExtensions).toEqual([
569
+ {
570
+ uri: 'urn:ietf:params:rtp-hdrext:sdes:mid',
571
+ id: 1,
572
+ encrypt: false,
573
+ parameters: {}
574
+ },
575
+ {
576
+ uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',
577
+ id: 4,
578
+ encrypt: false,
579
+ parameters: {}
580
+ },
581
+ {
582
+ uri: 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01',
583
+ id: 5,
584
+ encrypt: false,
585
+ parameters: {}
586
+ },
587
+ {
588
+ uri: 'urn:3gpp:video-orientation',
589
+ id: 11,
590
+ encrypt: false,
591
+ parameters: {}
592
+ },
593
+ {
594
+ uri: 'urn:ietf:params:rtp-hdrext:toffset',
595
+ id: 12,
596
+ encrypt: false,
597
+ parameters: {}
598
+ }
599
+ ]);
600
+ encodings = videoConsumer.rtpParameters.encodings;
601
+ expect(Array.isArray(encodings)).toBe(true);
602
+ expect(encodings.length).toBe(1);
603
+ expect(typeof encodings?.[0]).toBe('object');
604
+ expect(Object.keys(encodings[0])).toEqual(['ssrc', 'rtx', 'dtx']);
605
+ expect(typeof encodings?.[0].ssrc).toBe('number');
606
+ expect(typeof encodings?.[0].rtx).toBe('object');
607
+ expect(Object.keys(encodings[0].rtx)).toEqual(['ssrc']);
608
+ expect(typeof encodings?.[0].rtx?.ssrc).toBe('number');
609
+ rtcp = videoConsumer.rtpParameters.rtcp;
610
+ expect(typeof rtcp).toBe('object');
611
+ expect(typeof rtcp?.cname).toBe('string');
612
+ expect(videoConsumer.paused).toBe(false);
613
+ expect(videoConsumer.appData).toEqual({});
614
+ recvTransport.removeAllListeners('connect');
615
+ }, 500);
616
+ test('transport.consume() batches consumers created in same macrotask into the same task', async () => {
617
+ const videoConsumerRemoteParameters1 = fakeParameters.generateConsumerRemoteParameters({ codecMimeType: 'video/VP8' });
618
+ const videoConsumerRemoteParameters2 = fakeParameters.generateConsumerRemoteParameters({ codecMimeType: 'video/VP8' });
619
+ const pushSpy = jest.spyOn(recvTransport._awaitQueue, 'push');
620
+ const waitForConsumer = (id) => {
621
+ return new Promise((resolve) => {
622
+ recvTransport.observer.on('newconsumer', (consumer) => {
623
+ if (consumer.id === id) {
624
+ resolve();
625
+ }
626
+ });
627
+ });
628
+ };
629
+ const allConsumersCreated = Promise.all([
630
+ waitForConsumer(videoConsumerRemoteParameters1.id),
631
+ waitForConsumer(videoConsumerRemoteParameters2.id)
632
+ ]);
633
+ await Promise.all([
634
+ recvTransport.consume({
635
+ id: videoConsumerRemoteParameters1.id,
636
+ producerId: videoConsumerRemoteParameters1.producerId,
637
+ kind: videoConsumerRemoteParameters1.kind,
638
+ rtpParameters: videoConsumerRemoteParameters1.rtpParameters
639
+ }),
640
+ recvTransport.consume({
641
+ id: videoConsumerRemoteParameters2.id,
642
+ producerId: videoConsumerRemoteParameters2.producerId,
643
+ kind: videoConsumerRemoteParameters2.kind,
644
+ rtpParameters: videoConsumerRemoteParameters2.rtpParameters
645
+ })
646
+ ]);
647
+ await allConsumersCreated;
648
+ expect(pushSpy).toBeCalledTimes(1);
649
+ }, 500);
650
+ test('transport.consume() without remote Consumer parameters rejects with TypeError', async () => {
651
+ // @ts-ignore
652
+ await expect(recvTransport.consume({}))
653
+ .rejects
654
+ .toThrow(TypeError);
655
+ }, 500);
656
+ test('transport.consume() with missing remote Consumer parameters rejects with TypeError', async () => {
657
+ // @ts-ignore
658
+ await expect(recvTransport.consume({ id: '1234' }))
659
+ .rejects
660
+ .toThrow(TypeError);
661
+ // @ts-ignore
662
+ await expect(recvTransport.consume({ id: '1234', producerId: '4444' }))
663
+ .rejects
664
+ .toThrow(TypeError);
665
+ await expect(recvTransport.consume(
666
+ // @ts-ignore
667
+ {
668
+ id: '1234',
669
+ producerId: '4444',
670
+ kind: 'audio'
671
+ }))
672
+ .rejects
673
+ .toThrow(TypeError);
674
+ await expect(recvTransport.consume(
675
+ // @ts-ignore
676
+ {
677
+ id: '1234',
678
+ producerId: '4444',
679
+ kind: 'audio'
680
+ }))
681
+ .rejects
682
+ .toThrow(TypeError);
683
+ }, 500);
684
+ test('transport.consume() in a sending Transport rejects with UnsupportedError', async () => {
685
+ const { id, producerId, kind, rtpParameters } = fakeParameters.generateConsumerRemoteParameters({ codecMimeType: 'audio/opus' });
686
+ await expect(sendTransport.consume({
687
+ id,
688
+ producerId,
689
+ kind,
690
+ rtpParameters
691
+ }))
692
+ .rejects
693
+ .toThrow(errors_1.UnsupportedError);
694
+ }, 500);
695
+ test('transport.consume() with unsupported rtpParameters rejects with UnsupportedError', async () => {
696
+ const { id, producerId, kind, rtpParameters } = fakeParameters.generateConsumerRemoteParameters({ codecMimeType: 'audio/ISAC' });
697
+ await expect(sendTransport.consume({
698
+ id,
699
+ producerId,
700
+ kind,
701
+ rtpParameters
702
+ }))
703
+ .rejects
704
+ .toThrow(errors_1.UnsupportedError);
705
+ }, 500);
706
+ test('transport.consume() with a non object appData rejects with TypeError', async () => {
707
+ const consumerRemoteParameters = fakeParameters.generateConsumerRemoteParameters({ codecMimeType: 'audio/opus' });
708
+ // @ts-ignore
709
+ await expect(recvTransport.consume({ consumerRemoteParameters, appData: true }))
710
+ .rejects
711
+ .toThrow(TypeError);
712
+ }, 500);
713
+ test('transport.produceData() succeeds', async () => {
714
+ let dataProducerId;
715
+ let produceDataEventNumTimesCalled = 0;
716
+ // eslint-disable-next-line no-unused-vars
717
+ sendTransport.on('producedata', ({ sctpStreamParameters, label, protocol, appData }, callback /* errback */) => {
718
+ produceDataEventNumTimesCalled++;
719
+ expect(typeof sctpStreamParameters).toBe('object');
720
+ expect(label).toBe('FOO');
721
+ expect(protocol).toBe('BAR');
722
+ expect(appData).toEqual({ foo: 'FOO' });
723
+ const id = fakeParameters.generateDataProducerRemoteParameters().id;
724
+ dataProducerId = id;
725
+ // Emulate communication with the server and success response with Producer
726
+ // remote parameters.
727
+ setTimeout(() => callback({ id }));
728
+ });
729
+ dataProducer = await sendTransport.produceData({
730
+ ordered: false,
731
+ maxPacketLifeTime: 5555,
732
+ label: 'FOO',
733
+ protocol: 'BAR',
734
+ appData: { foo: 'FOO' }
735
+ });
736
+ expect(produceDataEventNumTimesCalled).toBe(1);
737
+ expect(typeof dataProducer).toBe('object');
738
+ expect(dataProducer.id).toBe(dataProducerId);
739
+ expect(dataProducer.closed).toBe(false);
740
+ expect(typeof dataProducer.sctpStreamParameters).toBe('object');
741
+ expect(typeof dataProducer.sctpStreamParameters.streamId).toBe('number');
742
+ expect(dataProducer.sctpStreamParameters.ordered).toBe(false);
743
+ expect(dataProducer.sctpStreamParameters.maxPacketLifeTime).toBe(5555);
744
+ expect(dataProducer.sctpStreamParameters.maxRetransmits).toBe(undefined);
745
+ expect(dataProducer.label).toBe('FOO');
746
+ expect(dataProducer.protocol).toBe('BAR');
747
+ sendTransport.removeAllListeners('producedata');
748
+ }, 500);
749
+ test('transport.produceData() in a receiving Transport rejects with UnsupportedError', async () => {
750
+ await expect(recvTransport.produceData({}))
751
+ .rejects
752
+ .toThrow(errors_1.UnsupportedError);
753
+ }, 500);
754
+ test('transport.produceData() with a non object appData rejects with TypeError', async () => {
755
+ // @ts-ignore
756
+ await expect(sendTransport.produceData({ appData: true }))
757
+ .rejects
758
+ .toThrow(TypeError);
759
+ }, 500);
760
+ test('transport.consumeData() succeeds', async () => {
761
+ const dataConsumerRemoteParameters = fakeParameters.generateDataConsumerRemoteParameters();
762
+ dataConsumer = await recvTransport.consumeData({
763
+ id: dataConsumerRemoteParameters.id,
764
+ dataProducerId: dataConsumerRemoteParameters.dataProducerId,
765
+ sctpStreamParameters: dataConsumerRemoteParameters.sctpStreamParameters,
766
+ label: 'FOO',
767
+ protocol: 'BAR',
768
+ appData: { bar: 'BAR' }
769
+ });
770
+ expect(typeof dataConsumer).toBe('object');
771
+ expect(dataConsumer.id).toBe(dataConsumerRemoteParameters.id);
772
+ expect(dataConsumer.dataProducerId).toBe(dataConsumerRemoteParameters.dataProducerId);
773
+ expect(dataConsumer.closed).toBe(false);
774
+ expect(typeof dataConsumer.sctpStreamParameters).toBe('object');
775
+ expect(typeof dataConsumer.sctpStreamParameters.streamId).toBe('number');
776
+ expect(dataConsumer.label).toBe('FOO');
777
+ expect(dataConsumer.protocol).toBe('BAR');
778
+ }, 500);
779
+ test('transport.consumeData() without remote DataConsumer parameters rejects with TypeError', async () => {
780
+ // @ts-ignore
781
+ await expect(recvTransport.consumeData({}))
782
+ .rejects
783
+ .toThrow(TypeError);
784
+ }, 500);
785
+ test('transport.consumeData() with missing remote DataConsumer parameters rejects with TypeError', async () => {
786
+ // @ts-ignore
787
+ await expect(recvTransport.consumeData({ id: '1234' }))
788
+ .rejects
789
+ .toThrow(TypeError);
790
+ // @ts-ignore
791
+ await expect(recvTransport.consumeData({ id: '1234', dataProducerId: '4444' }))
792
+ .rejects
793
+ .toThrow(TypeError);
794
+ }, 500);
795
+ test('transport.consumeData() in a sending Transport rejects with UnsupportedError', async () => {
796
+ const { id, dataProducerId, sctpStreamParameters } = fakeParameters.generateDataConsumerRemoteParameters();
797
+ await expect(sendTransport.consumeData({
798
+ id,
799
+ dataProducerId,
800
+ sctpStreamParameters
801
+ }))
802
+ .rejects
803
+ .toThrow(errors_1.UnsupportedError);
804
+ }, 500);
805
+ test('transport.consumeData() with a non object appData rejects with TypeError', async () => {
806
+ const dataConsumerRemoteParameters = fakeParameters.generateDataConsumerRemoteParameters();
807
+ // @ts-ignore
808
+ await expect(recvTransport.consumeData({ dataConsumerRemoteParameters, appData: true }))
809
+ .rejects
810
+ .toThrow(TypeError);
811
+ }, 500);
812
+ test('transport.getStats() succeeds', async () => {
813
+ const stats = await sendTransport.getStats();
814
+ expect(typeof stats).toBe('object');
815
+ }, 500);
816
+ test('transport.restartIce() succeeds', async () => {
817
+ await expect(sendTransport.restartIce({
818
+ iceParameters: {
819
+ usernameFragment: 'foo',
820
+ password: 'xxx'
821
+ }
822
+ }))
823
+ .resolves
824
+ .toBe(undefined);
825
+ }, 500);
826
+ test('transport.restartIce() without remote iceParameters rejects with TypeError', async () => {
827
+ // @ts-ignore
828
+ await expect(sendTransport.restartIce({}))
829
+ .rejects
830
+ .toThrow(TypeError);
831
+ }, 500);
832
+ test('transport.updateIceServers() succeeds', async () => {
833
+ await expect(sendTransport.updateIceServers({ iceServers: [] }))
834
+ .resolves
835
+ .toBe(undefined);
836
+ }, 500);
837
+ test('transport.updateIceServers() without iceServers rejects with TypeError', async () => {
838
+ await expect(sendTransport.updateIceServers({}))
839
+ .rejects
840
+ .toThrow(TypeError);
841
+ }, 500);
842
+ test('connection state change fires "connectionstatechange" in live Transport', () => {
843
+ let connectionStateChangeEventNumTimesCalled = 0;
844
+ sendTransport.on('connectionstatechange', (connectionState) => {
845
+ connectionStateChangeEventNumTimesCalled++;
846
+ expect(connectionState).toBe('completed');
847
+ });
848
+ // @ts-ignore
849
+ sendTransport.handler.setConnectionState('completed');
850
+ expect(connectionStateChangeEventNumTimesCalled).toBe(1);
851
+ expect(sendTransport.connectionState).toBe('completed');
852
+ sendTransport.removeAllListeners('connectionstatechange');
853
+ });
854
+ test('producer.pause() succeeds', () => {
855
+ videoProducer.pause();
856
+ expect(videoProducer.paused).toBe(true);
857
+ // Track will be still enabled due to disableTrackOnPause: false.
858
+ expect(videoProducer.track?.enabled).toBe(true);
859
+ });
860
+ test('producer.resume() succeeds', () => {
861
+ videoProducer.resume();
862
+ expect(videoProducer.paused).toBe(false);
863
+ expect(videoProducer.track?.enabled).toBe(true);
864
+ });
865
+ test('producer.replaceTrack() with a new track succeeds', async () => {
866
+ // Have the audio Producer paused.
867
+ audioProducer.pause();
868
+ const audioProducerPreviousTrack = audioProducer.track;
869
+ const newAudioTrack = new fake_mediastreamtrack_1.FakeMediaStreamTrack({ kind: 'audio' });
870
+ await expect(audioProducer.replaceTrack({ track: newAudioTrack }))
871
+ .resolves
872
+ .toBe(undefined);
873
+ // Previous track must be 'live' due to stopTracks: false.
874
+ expect(audioProducerPreviousTrack?.readyState).toBe('live');
875
+ expect(audioProducer.track?.readyState).toBe('live');
876
+ expect(audioProducer.track).not.toBe(audioProducerPreviousTrack);
877
+ expect(audioProducer.track).toBe(newAudioTrack);
878
+ // Producer was already paused.
879
+ expect(audioProducer.paused).toBe(true);
880
+ // Reset the audio paused state.
881
+ audioProducer.resume();
882
+ const videoProducerPreviousTrack = videoProducer.track;
883
+ const newVideoTrack = new fake_mediastreamtrack_1.FakeMediaStreamTrack({ kind: 'video' });
884
+ await expect(videoProducer.replaceTrack({ track: newVideoTrack }))
885
+ .resolves
886
+ .toBe(undefined);
887
+ // Previous track must be 'ended' due to stopTracks: true.
888
+ expect(videoProducerPreviousTrack?.readyState).toBe('ended');
889
+ expect(videoProducer.track).not.toBe(videoProducerPreviousTrack);
890
+ expect(videoProducer.track).toBe(newVideoTrack);
891
+ expect(videoProducer.paused).toBe(false);
892
+ }, 500);
893
+ test('producer.replaceTrack() with null succeeds', async () => {
894
+ // Have the audio Producer paused.
895
+ audioProducer.pause();
896
+ const audioProducerPreviousTrack = audioProducer.track;
897
+ await expect(audioProducer.replaceTrack({ track: null }))
898
+ .resolves
899
+ .toBe(undefined);
900
+ // Previous track must be 'live' due to stopTracks: false.
901
+ expect(audioProducerPreviousTrack?.readyState).toBe('live');
902
+ expect(audioProducer.track).toBeNull();
903
+ // Producer was already paused.
904
+ expect(audioProducer.paused).toBe(true);
905
+ // Reset the audio paused state.
906
+ audioProducer.resume();
907
+ expect(audioProducer.paused).toBe(false);
908
+ // Manually "mute" the original audio track.
909
+ audioProducerPreviousTrack.enabled = false;
910
+ // Set the original audio track back.
911
+ await expect(audioProducer.replaceTrack({ track: audioProducerPreviousTrack }))
912
+ .resolves
913
+ .toBe(undefined);
914
+ // The given audio track was muted but the Producer was not, so the track
915
+ // must not be muted now.
916
+ expect(audioProducer.paused).toBe(false);
917
+ expect(audioProducerPreviousTrack?.enabled).toBe(true);
918
+ // Reset the audio paused state.
919
+ audioProducer.resume();
920
+ }, 500);
921
+ test('producer.replaceTrack() with an ended track rejects with InvalidStateError', async () => {
922
+ const track = new fake_mediastreamtrack_1.FakeMediaStreamTrack({ kind: 'audio' });
923
+ track.stop();
924
+ await expect(videoProducer.replaceTrack({ track }))
925
+ .rejects
926
+ .toThrow(errors_1.InvalidStateError);
927
+ expect(track.readyState).toBe('ended');
928
+ expect(videoProducer.track?.readyState).toBe('live');
929
+ }, 500);
930
+ test('producer.replaceTrack() with the same track succeeds', async () => {
931
+ await expect(audioProducer.replaceTrack({ track: audioProducer.track }))
932
+ .resolves
933
+ .toBe(undefined);
934
+ expect(audioProducer.track?.readyState).toBe('live');
935
+ }, 500);
936
+ test('producer.setMaxSpatialLayer() succeeds', async () => {
937
+ await expect(videoProducer.setMaxSpatialLayer(0))
938
+ .resolves
939
+ .toBe(undefined);
940
+ expect(videoProducer.maxSpatialLayer).toBe(0);
941
+ }, 500);
942
+ test('producer.setMaxSpatialLayer() in an audio Producer rejects with UnsupportedError', async () => {
943
+ await expect(audioProducer.setMaxSpatialLayer(1))
944
+ .rejects
945
+ .toThrow(errors_1.UnsupportedError);
946
+ expect(audioProducer.maxSpatialLayer).toBe(undefined);
947
+ }, 500);
948
+ test('producer.setMaxSpatialLayer() with invalid spatialLayer rejects with TypeError', async () => {
949
+ // @ts-ignore
950
+ await expect(videoProducer.setMaxSpatialLayer('chicken'))
951
+ .rejects
952
+ .toThrow(TypeError);
953
+ }, 500);
954
+ test('producer.setMaxSpatialLayer() without spatialLayer rejects with TypeError', async () => {
955
+ // @ts-ignore
956
+ await expect(videoProducer.setMaxSpatialLayer())
957
+ .rejects
958
+ .toThrow(TypeError);
959
+ }, 500);
960
+ test('producer.setRtpEncodingParameters() succeeds', async () => {
961
+ await expect(videoProducer.setRtpEncodingParameters({ scaleResolutionDownBy: 2 }))
962
+ .resolves
963
+ .toBe(undefined);
964
+ expect(videoProducer.maxSpatialLayer).toBe(0);
965
+ }, 500);
966
+ test('producer.getStats() succeeds', async () => {
967
+ const stats = await videoProducer.getStats();
968
+ expect(typeof stats).toBe('object');
969
+ }, 500);
970
+ test('consumer.resume() succeeds', () => {
971
+ videoConsumer.resume();
972
+ expect(videoConsumer.paused).toBe(false);
973
+ });
974
+ test('consumer.pause() succeeds', () => {
975
+ videoConsumer.pause();
976
+ expect(videoConsumer.paused).toBe(true);
977
+ });
978
+ test('consumer.getStats() succeeds', async () => {
979
+ const stats = await videoConsumer.getStats();
980
+ expect(typeof stats).toBe('object');
981
+ }, 500);
982
+ test('producer.close() succeed', () => {
983
+ audioProducer.close();
984
+ expect(audioProducer.closed).toBe(true);
985
+ // Track will be still 'live' due to stopTracks: false.
986
+ expect(audioProducer.track?.readyState).toBe('live');
987
+ });
988
+ test('producer.replaceTrack() rejects with InvalidStateError if closed', async () => {
989
+ const audioTrack = new fake_mediastreamtrack_1.FakeMediaStreamTrack({ kind: 'audio' });
990
+ await expect(audioProducer.replaceTrack({ track: audioTrack }))
991
+ .rejects
992
+ .toThrow(errors_1.InvalidStateError);
993
+ expect(audioTrack.readyState).toBe('live');
994
+ }, 500);
995
+ test('producer.getStats() rejects with InvalidStateError if closed', async () => {
996
+ await expect(audioProducer.getStats())
997
+ .rejects
998
+ .toThrow(errors_1.InvalidStateError);
999
+ }, 500);
1000
+ test('consumer.close() succeed', () => {
1001
+ audioConsumer.close();
1002
+ expect(audioConsumer.closed).toBe(true);
1003
+ expect(audioConsumer.track.readyState).toBe('ended');
1004
+ });
1005
+ test('consumer.getStats() rejects with InvalidStateError if closed', async () => {
1006
+ await expect(audioConsumer.getStats())
1007
+ .rejects
1008
+ .toThrow(errors_1.InvalidStateError);
1009
+ }, 500);
1010
+ test('dataProducer.close() succeed', () => {
1011
+ dataProducer.close();
1012
+ expect(dataProducer.closed).toBe(true);
1013
+ });
1014
+ test('dataConsumer.close() succeed', () => {
1015
+ dataConsumer.close();
1016
+ expect(dataConsumer.closed).toBe(true);
1017
+ });
1018
+ test('remotetely stopped track fires "trackended" in live Producers/Consumers', () => {
1019
+ let audioProducerTrackendedEventCalled = false;
1020
+ let videoProducerTrackendedEventCalled = false;
1021
+ let audiosConsumerTrackendedEventCalled = false;
1022
+ let videoConsumerTrackendedEventCalled = false;
1023
+ audioProducer.on('trackended', () => {
1024
+ audioProducerTrackendedEventCalled = true;
1025
+ });
1026
+ videoProducer.on('trackended', () => {
1027
+ videoProducerTrackendedEventCalled = true;
1028
+ });
1029
+ audioConsumer.on('trackended', () => {
1030
+ audiosConsumerTrackendedEventCalled = true;
1031
+ });
1032
+ videoConsumer.on('trackended', () => {
1033
+ videoConsumerTrackendedEventCalled = true;
1034
+ });
1035
+ // @ts-ignore
1036
+ audioProducer.track.remoteStop();
1037
+ // Audio Producer was already closed.
1038
+ expect(audioProducerTrackendedEventCalled).toBe(false);
1039
+ // @ts-ignore
1040
+ videoProducer.track.remoteStop();
1041
+ expect(videoProducerTrackendedEventCalled).toBe(true);
1042
+ // @ts-ignore
1043
+ audioConsumer.track.remoteStop();
1044
+ // Audio Consumer was already closed.
1045
+ expect(audiosConsumerTrackendedEventCalled).toBe(false);
1046
+ // @ts-ignore
1047
+ videoConsumer.track.remoteStop();
1048
+ expect(videoConsumerTrackendedEventCalled).toBe(true);
1049
+ audioProducer.removeAllListeners();
1050
+ videoProducer.removeAllListeners();
1051
+ audioConsumer.removeAllListeners();
1052
+ videoConsumer.removeAllListeners();
1053
+ });
1054
+ test('transport.close() fires "transportclose" in live Producers/Consumers', () => {
1055
+ let audioProducerTransportcloseEventCalled = false;
1056
+ let videoProducerTransportcloseEventCalled = false;
1057
+ let audioConsumerTransportcloseEventCalled = false;
1058
+ let videoConsumerTransportcloseEventCalled = false;
1059
+ audioProducer.on('transportclose', () => {
1060
+ audioProducerTransportcloseEventCalled = true;
1061
+ });
1062
+ videoProducer.on('transportclose', () => {
1063
+ videoProducerTransportcloseEventCalled = true;
1064
+ });
1065
+ audioConsumer.on('transportclose', () => {
1066
+ audioConsumerTransportcloseEventCalled = true;
1067
+ });
1068
+ videoConsumer.on('transportclose', () => {
1069
+ videoConsumerTransportcloseEventCalled = true;
1070
+ });
1071
+ // Audio Producer was already closed.
1072
+ expect(audioProducer.closed).toBe(true);
1073
+ expect(videoProducer.closed).toBe(false);
1074
+ sendTransport.close();
1075
+ expect(sendTransport.closed).toBe(true);
1076
+ expect(videoProducer.closed).toBe(true);
1077
+ // Audio Producer was already closed.
1078
+ expect(audioProducerTransportcloseEventCalled).toBe(false);
1079
+ expect(videoProducerTransportcloseEventCalled).toBe(true);
1080
+ // Audio Consumer was already closed.
1081
+ expect(audioConsumer.closed).toBe(true);
1082
+ expect(videoConsumer.closed).toBe(false);
1083
+ recvTransport.close();
1084
+ expect(recvTransport.closed).toBe(true);
1085
+ expect(videoConsumer.closed).toBe(true);
1086
+ // Audio Consumer was already closed.
1087
+ expect(audioConsumerTransportcloseEventCalled).toBe(false);
1088
+ expect(videoConsumerTransportcloseEventCalled).toBe(true);
1089
+ audioProducer.removeAllListeners();
1090
+ videoProducer.removeAllListeners();
1091
+ audioConsumer.removeAllListeners();
1092
+ videoConsumer.removeAllListeners();
1093
+ });
1094
+ test('transport.produce() rejects with InvalidStateError if closed', async () => {
1095
+ const track = new fake_mediastreamtrack_1.FakeMediaStreamTrack({ kind: 'audio' });
1096
+ // Add noop listener to avoid the method fail.
1097
+ sendTransport.on('produce', () => { });
1098
+ await expect(sendTransport.produce({ track, stopTracks: false }))
1099
+ .rejects
1100
+ .toThrow(errors_1.InvalidStateError);
1101
+ // The track must be 'live' due to stopTracks: false.
1102
+ expect(track.readyState).toBe('live');
1103
+ sendTransport.removeAllListeners('produce');
1104
+ }, 500);
1105
+ test('transport.consume() rejects with InvalidStateError if closed', async () => {
1106
+ // @ts-ignore
1107
+ await expect(recvTransport.consume({}))
1108
+ .rejects
1109
+ .toThrow(errors_1.InvalidStateError);
1110
+ recvTransport.removeAllListeners();
1111
+ }, 500);
1112
+ test('transport.produceData() rejects with InvalidStateError if closed', async () => {
1113
+ // Add noop listener to avoid the method fail.
1114
+ sendTransport.on('producedata', () => { });
1115
+ await expect(sendTransport.produceData({}))
1116
+ .rejects
1117
+ .toThrow(errors_1.InvalidStateError);
1118
+ sendTransport.removeAllListeners('producedata');
1119
+ }, 500);
1120
+ test('transport.consumeData() rejects with InvalidStateError if closed', async () => {
1121
+ // @ts-ignore
1122
+ await expect(recvTransport.consumeData({}))
1123
+ .rejects
1124
+ .toThrow(errors_1.InvalidStateError);
1125
+ }, 500);
1126
+ test('transport.getStats() rejects with InvalidStateError if closed', async () => {
1127
+ await expect(sendTransport.getStats())
1128
+ .rejects
1129
+ .toThrow(errors_1.InvalidStateError);
1130
+ }, 500);
1131
+ test('transport.restartIce() rejects with InvalidStateError if closed', async () => {
1132
+ // @ts-ignore
1133
+ await expect(sendTransport.restartIce({ ieParameters: {} }))
1134
+ .rejects
1135
+ .toThrow(errors_1.InvalidStateError);
1136
+ }, 500);
1137
+ test('transport.updateIceServers() rejects with InvalidStateError if closed', async () => {
1138
+ await expect(sendTransport.updateIceServers({ iceServers: [] }))
1139
+ .rejects
1140
+ .toThrow(errors_1.InvalidStateError);
1141
+ }, 500);
1142
+ test('connection state change does not fire "connectionstatechange" in closed Transport', () => {
1143
+ let connectionStateChangeEventNumTimesCalled = 0;
1144
+ // eslint-disable-next-line no-unused-vars
1145
+ sendTransport.on('connectionstatechange', ( /* connectionState */) => {
1146
+ connectionStateChangeEventNumTimesCalled++;
1147
+ });
1148
+ // @ts-ignore
1149
+ sendTransport.handler.setConnectionState('disconnected');
1150
+ expect(connectionStateChangeEventNumTimesCalled).toBe(0);
1151
+ expect(sendTransport.connectionState).toBe('disconnected');
1152
+ sendTransport.removeAllListeners('connectionstatechange');
1153
+ });
1154
+ test('RemoteSdp properly handles multiple streams of the same type in planB', async () => {
1155
+ let sdp = undefined;
1156
+ let sdpObject = undefined;
1157
+ const remoteSdp = new RemoteSdp_1.RemoteSdp({ planB: true });
1158
+ await remoteSdp.receive({
1159
+ mid: 'video',
1160
+ kind: 'video',
1161
+ offerRtpParameters: fakeParameters.generateConsumerRemoteParameters({ codecMimeType: 'video/VP8' }).rtpParameters,
1162
+ streamId: 'streamId-1',
1163
+ trackId: 'trackId-1'
1164
+ });
1165
+ sdp = remoteSdp.getSdp();
1166
+ sdpObject = sdpTransform.parse(sdp);
1167
+ expect(sdpObject.media.length).toBe(1);
1168
+ expect(sdpObject.media[0].payloads).toBe('101 102');
1169
+ expect(sdpObject.media[0].rtp.length).toBe(2);
1170
+ expect(sdpObject.media[0].rtp[0].payload).toBe(101);
1171
+ expect(sdpObject.media[0].rtp[0].codec).toBe('VP8');
1172
+ expect(sdpObject.media[0].rtp[1].payload).toBe(102);
1173
+ expect(sdpObject.media[0].rtp[1].codec).toBe('rtx');
1174
+ expect(sdpObject.media[0].ssrcs?.length).toBe(4);
1175
+ await remoteSdp.receive({
1176
+ mid: 'video',
1177
+ kind: 'video',
1178
+ offerRtpParameters: fakeParameters.generateConsumerRemoteParameters({ codecMimeType: 'video/H264' }).rtpParameters,
1179
+ streamId: 'streamId-2',
1180
+ trackId: 'trackId-2'
1181
+ });
1182
+ sdp = remoteSdp.getSdp();
1183
+ sdpObject = sdpTransform.parse(sdp);
1184
+ expect(sdpObject.media.length).toBe(1);
1185
+ expect(sdpObject.media[0].payloads).toBe('101 102 103 104');
1186
+ expect(sdpObject.media[0].rtp.length).toBe(4);
1187
+ expect(sdpObject.media[0].rtp[0].payload).toBe(101);
1188
+ expect(sdpObject.media[0].rtp[0].codec).toBe('VP8');
1189
+ expect(sdpObject.media[0].rtp[1].payload).toBe(102);
1190
+ expect(sdpObject.media[0].rtp[1].codec).toBe('rtx');
1191
+ expect(sdpObject.media[0].rtp[2].payload).toBe(103);
1192
+ expect(sdpObject.media[0].rtp[2].codec).toBe('H264');
1193
+ expect(sdpObject.media[0].rtp[3].payload).toBe(104);
1194
+ expect(sdpObject.media[0].rtp[3].codec).toBe('rtx');
1195
+ expect(sdpObject.media[0].ssrcs?.length).toBe(8);
1196
+ await remoteSdp.planBStopReceiving({
1197
+ mid: 'video',
1198
+ offerRtpParameters: fakeParameters.generateConsumerRemoteParameters({ codecMimeType: 'video/H264' }).rtpParameters
1199
+ });
1200
+ sdp = remoteSdp.getSdp();
1201
+ sdpObject = sdpTransform.parse(sdp);
1202
+ expect(sdpObject.media.length).toBe(1);
1203
+ expect(sdpObject.media[0].payloads).toBe('101 102 103 104');
1204
+ expect(sdpObject.media[0].rtp.length).toBe(4);
1205
+ expect(sdpObject.media[0].rtp[0].payload).toBe(101);
1206
+ expect(sdpObject.media[0].rtp[0].codec).toBe('VP8');
1207
+ expect(sdpObject.media[0].rtp[1].payload).toBe(102);
1208
+ expect(sdpObject.media[0].rtp[1].codec).toBe('rtx');
1209
+ expect(sdpObject.media[0].rtp[2].payload).toBe(103);
1210
+ expect(sdpObject.media[0].rtp[2].codec).toBe('H264');
1211
+ expect(sdpObject.media[0].rtp[3].payload).toBe(104);
1212
+ expect(sdpObject.media[0].rtp[3].codec).toBe('rtx');
1213
+ expect(sdpObject.media[0].ssrcs?.length).toBe(4);
1214
+ }, 500);
1215
+ test('RemoteSdp does not duplicate codec descriptions', async () => {
1216
+ let sdp = undefined;
1217
+ let sdpObject = undefined;
1218
+ const remoteSdp = new RemoteSdp_1.RemoteSdp({ planB: true });
1219
+ await remoteSdp.receive({
1220
+ mid: 'video',
1221
+ kind: 'video',
1222
+ offerRtpParameters: fakeParameters.generateConsumerRemoteParameters({ codecMimeType: 'video/VP8' }).rtpParameters,
1223
+ streamId: 'streamId-1',
1224
+ trackId: 'trackId-1'
1225
+ });
1226
+ sdp = remoteSdp.getSdp();
1227
+ sdpObject = sdpTransform.parse(sdp);
1228
+ expect(sdpObject.media.length).toBe(1);
1229
+ expect(sdpObject.media[0].payloads).toBe('101 102');
1230
+ expect(sdpObject.media[0].rtp.length).toBe(2);
1231
+ expect(sdpObject.media[0].rtp[0].payload).toBe(101);
1232
+ expect(sdpObject.media[0].rtp[0].codec).toBe('VP8');
1233
+ expect(sdpObject.media[0].rtp[1].payload).toBe(102);
1234
+ expect(sdpObject.media[0].rtp[1].codec).toBe('rtx');
1235
+ expect(sdpObject.media[0].ssrcs?.length).toBe(4);
1236
+ await remoteSdp.receive({
1237
+ mid: 'video',
1238
+ kind: 'video',
1239
+ offerRtpParameters: fakeParameters.generateConsumerRemoteParameters({ codecMimeType: 'video/VP8' }).rtpParameters,
1240
+ streamId: 'streamId-1',
1241
+ trackId: 'trackId-1'
1242
+ });
1243
+ sdp = remoteSdp.getSdp();
1244
+ sdpObject = sdpTransform.parse(sdp);
1245
+ expect(sdpObject.media.length).toBe(1);
1246
+ expect(sdpObject.media[0].payloads).toBe('101 102');
1247
+ expect(sdpObject.media[0].rtp.length).toBe(2);
1248
+ expect(sdpObject.media[0].rtp[0].payload).toBe(101);
1249
+ expect(sdpObject.media[0].rtp[0].codec).toBe('VP8');
1250
+ expect(sdpObject.media[0].rtp[1].payload).toBe(102);
1251
+ expect(sdpObject.media[0].rtp[1].codec).toBe('rtx');
1252
+ expect(sdpObject.media[0].ssrcs?.length).toBe(8);
1253
+ }, 500);
1254
+ test('parseScalabilityMode() works', () => {
1255
+ expect(parseScalabilityMode('L1T3')).toEqual({ spatialLayers: 1, temporalLayers: 3 });
1256
+ expect(parseScalabilityMode('L3T2_KEY')).toEqual({ spatialLayers: 3, temporalLayers: 2 });
1257
+ expect(parseScalabilityMode('S2T3')).toEqual({ spatialLayers: 2, temporalLayers: 3 });
1258
+ expect(parseScalabilityMode('foo')).toEqual({ spatialLayers: 1, temporalLayers: 1 });
1259
+ expect(parseScalabilityMode()).toEqual({ spatialLayers: 1, temporalLayers: 1 });
1260
+ expect(parseScalabilityMode('S0T3')).toEqual({ spatialLayers: 1, temporalLayers: 1 });
1261
+ expect(parseScalabilityMode('S1T0')).toEqual({ spatialLayers: 1, temporalLayers: 1 });
1262
+ expect(parseScalabilityMode('L20T3')).toEqual({ spatialLayers: 20, temporalLayers: 3 });
1263
+ expect(parseScalabilityMode('S200T3')).toEqual({ spatialLayers: 1, temporalLayers: 1 });
1264
+ });
1265
+ describe('detectDevice() assigns proper handler based on UserAgent', () => {
1266
+ const originalNavigator = global.navigator;
1267
+ for (const uaTestCase of uaTestCases_1.uaTestCases) {
1268
+ test(uaTestCase.desc, () => {
1269
+ // @ts-ignore
1270
+ global.navigator =
1271
+ {
1272
+ userAgent: uaTestCase.ua
1273
+ };
1274
+ const originalRTCRtpTransceiver = global.RTCRtpTransceiver;
1275
+ if (uaTestCase.expect === 'Safari12') {
1276
+ global.RTCRtpTransceiver = class Dummy {
1277
+ currentDirection() { }
1278
+ };
1279
+ }
1280
+ expect(detectDevice()).toBe(uaTestCase.expect);
1281
+ // Cleanup.
1282
+ global.RTCRtpTransceiver = originalRTCRtpTransceiver;
1283
+ }, 100);
1284
+ }
1285
+ // Cleanup.
1286
+ global.navigator = originalNavigator;
1287
+ });