@louis-qode/mediasoup 1.0.0 → 1.0.2

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.

Potentially problematic release.


This version of @louis-qode/mediasoup might be problematic. Click here for more details.

Files changed (57) hide show
  1. package/dist/Logger.d.ts +13 -0
  2. package/dist/Logger.d.ts.map +1 -0
  3. package/dist/Logger.js +31 -0
  4. package/dist/Logger.js.map +1 -0
  5. package/dist/RoomClient.d.ts +200 -0
  6. package/dist/RoomClient.d.ts.map +1 -0
  7. package/dist/RoomClient.js +1620 -0
  8. package/dist/RoomClient.js.map +1 -0
  9. package/dist/RoomContext.d.ts +57 -0
  10. package/dist/RoomContext.d.ts.map +1 -0
  11. package/dist/RoomContext.js +14 -0
  12. package/dist/RoomContext.js.map +1 -0
  13. package/dist/cookiesManager.d.ts +16 -0
  14. package/dist/cookiesManager.d.ts.map +1 -0
  15. package/dist/cookiesManager.js +28 -0
  16. package/dist/cookiesManager.js.map +1 -0
  17. package/dist/deviceInfo.d.ts +3 -0
  18. package/dist/deviceInfo.d.ts.map +1 -0
  19. package/dist/deviceInfo.js +24 -0
  20. package/dist/deviceInfo.js.map +1 -0
  21. package/dist/e2e.d.ts +10 -0
  22. package/dist/e2e.d.ts.map +1 -0
  23. package/dist/e2e.js +71 -0
  24. package/dist/e2e.js.map +1 -0
  25. package/dist/index.d.ts +4 -3
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +4 -6
  28. package/dist/index.js.map +1 -1
  29. package/dist/lib/Logger.d.ts +13 -0
  30. package/dist/lib/Logger.d.ts.map +1 -0
  31. package/dist/lib/Logger.js +31 -0
  32. package/dist/lib/Logger.js.map +1 -0
  33. package/dist/lib/RoomClient.d.ts +200 -0
  34. package/dist/lib/RoomClient.d.ts.map +1 -0
  35. package/dist/lib/RoomClient.js +1620 -0
  36. package/dist/lib/RoomClient.js.map +1 -0
  37. package/dist/lib/cookiesManager.d.ts +16 -0
  38. package/dist/lib/cookiesManager.d.ts.map +1 -0
  39. package/dist/lib/cookiesManager.js +28 -0
  40. package/dist/lib/cookiesManager.js.map +1 -0
  41. package/dist/lib/deviceInfo.d.ts +3 -0
  42. package/dist/lib/deviceInfo.d.ts.map +1 -0
  43. package/dist/lib/deviceInfo.js +24 -0
  44. package/dist/lib/deviceInfo.js.map +1 -0
  45. package/dist/lib/e2e.d.ts +10 -0
  46. package/dist/lib/e2e.d.ts.map +1 -0
  47. package/dist/lib/e2e.js +71 -0
  48. package/dist/lib/e2e.js.map +1 -0
  49. package/dist/lib/urlFactory.d.ts +7 -0
  50. package/dist/lib/urlFactory.d.ts.map +1 -0
  51. package/dist/lib/urlFactory.js +4 -0
  52. package/dist/lib/urlFactory.js.map +1 -0
  53. package/dist/urlFactory.d.ts +7 -0
  54. package/dist/urlFactory.d.ts.map +1 -0
  55. package/dist/urlFactory.js +4 -0
  56. package/dist/urlFactory.js.map +1 -0
  57. package/package.json +15 -4
@@ -0,0 +1,1620 @@
1
+ import { AwaitQueue } from 'awaitqueue';
2
+ import * as mediasoupClient from 'mediasoup-client';
3
+ import protooClient from 'protoo-client';
4
+ import * as cookiesManager from './cookiesManager';
5
+ import * as e2e from './e2e';
6
+ import Logger from './Logger';
7
+ import * as requestActions from './redux/requestActions';
8
+ import * as stateActions from './redux/stateActions';
9
+ import { store } from './redux/store';
10
+ import { getProtooUrl } from './urlFactory';
11
+ const WEBCAM_VIDEO_CONSTRAINS = {
12
+ qvga: { width: { ideal: 320 }, height: { ideal: 240 } },
13
+ vga: { width: { ideal: 640 }, height: { ideal: 480 } },
14
+ hd: { width: { ideal: 1280 }, height: { ideal: 720 } },
15
+ };
16
+ const SCREEN_SHARING_VIDEO_CONSTRAINS = {
17
+ qvga: { width: { ideal: 320 }, height: { ideal: 240 } },
18
+ vga: { width: { ideal: 640 }, height: { ideal: 480 } },
19
+ hd: { width: { ideal: 1280 }, height: { ideal: 720 } },
20
+ '4k': { width: { ideal: 3840 }, height: { ideal: 2160 } },
21
+ };
22
+ const PC_PROPRIETARY_CONSTRAINTS = {};
23
+ const EXTERNAL_VIDEO_SRC = '/videos/video-audio-stereo.mp4';
24
+ const logger = new Logger('RoomClient');
25
+ export default class RoomClient {
26
+ constructor({ dispatch, roomId, peerId, displayName, device, handlerName, forceTcp, produce, consume, mic, webcam, datachannel, enableWebcamLayers, enableSharingLayers, webcamScalabilityMode, sharingScalabilityMode, numWebcamSimulcastStreams, numSharingSimulcastStreams, videoContentHint, screenSharing4K, preferLocalCodecsOrder, forcePCMA, forceVP8, forceH264, forceVP9, forceAV1, externalVideo, e2eKey, stats, }) {
27
+ logger.debug('constructor() [roomId:"%s", peerId:"%s", displayName:"%s", device:%s]', roomId, peerId, displayName, device.flag);
28
+ this._dispatch = dispatch;
29
+ this._closed = false;
30
+ this._displayName = displayName;
31
+ this._device = device;
32
+ this._handlerName = handlerName;
33
+ this._forceTcp = forceTcp;
34
+ this._produce = produce;
35
+ this._consume = consume;
36
+ this._useMic = Boolean(mic);
37
+ this._useWebcam = webcam;
38
+ this._useDataChannel = Boolean(datachannel);
39
+ this._preferLocalCodecsOrder = Boolean(preferLocalCodecsOrder);
40
+ this._forcePCMA = Boolean(forcePCMA);
41
+ this._forceVP8 = Boolean(forceVP8);
42
+ this._forceH264 = Boolean(forceH264);
43
+ this._forceVP9 = Boolean(forceVP9);
44
+ this._forceAV1 = Boolean(forceAV1);
45
+ this._enableWebcamLayers = Boolean(enableWebcamLayers);
46
+ this._enableSharingLayers = Boolean(enableSharingLayers);
47
+ this._webcamScalabilityMode = webcamScalabilityMode;
48
+ this._sharingScalabilityMode = sharingScalabilityMode;
49
+ this._numWebcamSimulcastStreams = numWebcamSimulcastStreams;
50
+ this._numSharingSimulcastStreams = numSharingSimulcastStreams;
51
+ //
52
+ this._videoContentHint = videoContentHint || '';
53
+ this._screenSharing4K = Boolean(screenSharing4K);
54
+ this._externalVideo = null;
55
+ this._e2eKey = e2eKey;
56
+ this._stats = stats;
57
+ this._externalVideoStream = null;
58
+ this._nextDataChannelTestNumber = 0;
59
+ this._consumingAwaitQueue = new AwaitQueue();
60
+ if (externalVideo) {
61
+ this._externalVideo = document.createElement('video');
62
+ this._externalVideo.controls = true;
63
+ this._externalVideo.muted = true;
64
+ this._externalVideo.loop = true;
65
+ this._externalVideo.setAttribute('playsinline', '');
66
+ this._externalVideo.src = EXTERNAL_VIDEO_SRC;
67
+ this._externalVideo
68
+ .play()
69
+ .catch((error) => logger.warn('externalVideo.play() failed:%o', error));
70
+ }
71
+ this._protooUrl = getProtooUrl({
72
+ roomId,
73
+ peerId,
74
+ });
75
+ this._protoo = null;
76
+ this._mediasoupDevice = null;
77
+ this._sendTransport = null;
78
+ this._recvTransport = null;
79
+ this._micProducer = null;
80
+ this._webcamProducer = null;
81
+ this._shareProducer = null;
82
+ this._chatDataProducer = null;
83
+ this._botDataProducer = null;
84
+ this._consumers = new Map();
85
+ this._dataConsumers = new Map();
86
+ this._webcams = new Map();
87
+ this._webcam = {
88
+ device: null,
89
+ resolution: 'hd',
90
+ };
91
+ if (this._e2eKey && e2e.isSupported()) {
92
+ e2e.setCryptoKey('setCryptoKey', this._e2eKey, true);
93
+ }
94
+ }
95
+ close() {
96
+ if (this._closed)
97
+ return;
98
+ this._closed = true;
99
+ logger.debug('close()');
100
+ this._protoo.close();
101
+ if (this._sendTransport) {
102
+ this._sendTransport.close();
103
+ this._sendTransport = null;
104
+ }
105
+ if (this._recvTransport) {
106
+ this._recvTransport.close();
107
+ this._recvTransport = null;
108
+ }
109
+ this._dispatch(stateActions.setRoomState('closed'));
110
+ }
111
+ async join() {
112
+ this._dispatch(stateActions.setMediasoupClientVersion(mediasoupClient.version));
113
+ const protooTransport = new protooClient.WebSocketTransport(this._protooUrl, {
114
+ origin: 'https://louis.internal.qode.world'
115
+ });
116
+ this._protoo = new protooClient.Peer(protooTransport);
117
+ this._dispatch(stateActions.setRoomState('connecting'));
118
+ this._protoo.on('open', () => this._joinRoom());
119
+ this._protoo.on('failed', () => {
120
+ this._dispatch(requestActions.notify({
121
+ type: 'error',
122
+ text: 'WebSocket connection failed',
123
+ }));
124
+ });
125
+ this._protoo.on('disconnected', () => {
126
+ this._dispatch(requestActions.notify({
127
+ type: 'error',
128
+ text: 'WebSocket disconnected',
129
+ }));
130
+ if (this._sendTransport) {
131
+ this._sendTransport.close();
132
+ this._sendTransport = null;
133
+ }
134
+ if (this._recvTransport) {
135
+ this._recvTransport.close();
136
+ this._recvTransport = null;
137
+ }
138
+ this._dispatch(stateActions.setRoomState('closed'));
139
+ });
140
+ this._protoo.on('close', () => {
141
+ if (this._closed)
142
+ return;
143
+ this.close();
144
+ });
145
+ this._protoo.on('request', async (request, accept, reject) => {
146
+ logger.debug('proto "request" event [method:%s, data:%o]', request.method, request.data);
147
+ switch (request.method) {
148
+ case 'newConsumer': {
149
+ await this._consumingAwaitQueue.push(async () => {
150
+ if (!this._consume) {
151
+ reject(403, 'I do not want to consume');
152
+ return;
153
+ }
154
+ const { peerId, consumerId, producerId, kind, rtpParameters, type, producerPaused, consumerScore, appData, } = request.data;
155
+ try {
156
+ const consumer = await this._recvTransport.consume({
157
+ id: consumerId,
158
+ producerId,
159
+ kind,
160
+ rtpParameters,
161
+ streamId: `${peerId}-${appData.source === 'screensharing' ? 'screensharing' : 'audio-video'}`,
162
+ appData: { ...appData, peerId },
163
+ });
164
+ if (this._e2eKey && e2e.isSupported() && consumer.rtpReceiver) {
165
+ e2e.setupReceiverTransform(consumer.rtpReceiver);
166
+ }
167
+ this._consumers.set(consumer.id, consumer);
168
+ consumer.on('transportclose', () => {
169
+ this._consumers.delete(consumer.id);
170
+ });
171
+ const { spatialLayers, temporalLayers } = mediasoupClient.parseScalabilityMode(consumer.rtpParameters.encodings[0].scalabilityMode);
172
+ this._dispatch(stateActions.addConsumer({
173
+ id: consumer.id,
174
+ type: type,
175
+ locallyPaused: false,
176
+ remotelyPaused: producerPaused,
177
+ rtpParameters: consumer.rtpParameters,
178
+ spatialLayers: spatialLayers,
179
+ temporalLayers: temporalLayers,
180
+ preferredSpatialLayer: spatialLayers - 1,
181
+ preferredTemporalLayer: temporalLayers - 1,
182
+ priority: 1,
183
+ codec: consumer.rtpParameters.codecs[0].mimeType.split('/')[1],
184
+ track: consumer.track,
185
+ }, peerId));
186
+ this._dispatch(stateActions.setConsumerScore(consumerId, consumerScore));
187
+ accept();
188
+ if (consumer.kind === 'video' && store.getState().me.audioOnly)
189
+ this._pauseConsumer(consumer);
190
+ }
191
+ catch (error) {
192
+ logger.error('"newConsumer" request failed:%o', error);
193
+ this._dispatch(requestActions.notify({
194
+ type: 'error',
195
+ text: `Error creating a Consumer: ${error}`,
196
+ }));
197
+ throw error;
198
+ }
199
+ });
200
+ break;
201
+ }
202
+ case 'newDataConsumer': {
203
+ await this._consumingAwaitQueue.push(async () => {
204
+ if (!this._consume) {
205
+ reject(403, 'I do not want to data consume');
206
+ return;
207
+ }
208
+ if (!this._useDataChannel) {
209
+ reject(403, 'I do not want DataChannels');
210
+ return;
211
+ }
212
+ const { peerId, dataConsumerId, dataProducerId, sctpStreamParameters, label, protocol, appData, } = request.data;
213
+ try {
214
+ const dataConsumer = await this._recvTransport.consumeData({
215
+ id: dataConsumerId,
216
+ dataProducerId,
217
+ sctpStreamParameters,
218
+ label,
219
+ protocol,
220
+ appData: { ...appData, peerId },
221
+ });
222
+ this._dataConsumers.set(dataConsumer.id, dataConsumer);
223
+ dataConsumer.on('transportclose', () => {
224
+ this._dataConsumers.delete(dataConsumer.id);
225
+ });
226
+ dataConsumer.on('open', () => {
227
+ logger.debug('DataConsumer "open" event');
228
+ });
229
+ dataConsumer.on('close', () => {
230
+ logger.warn('DataConsumer "close" event');
231
+ this._dataConsumers.delete(dataConsumer.id);
232
+ });
233
+ dataConsumer.on('error', (error) => {
234
+ logger.error('DataConsumer "error" event:%o', error);
235
+ this._dispatch(requestActions.notify({
236
+ type: 'error',
237
+ text: `DataConsumer error: ${error}`,
238
+ }));
239
+ });
240
+ dataConsumer.on('message', (message) => {
241
+ logger.debug('DataConsumer "message" event [streamId:%d]', dataConsumer.sctpStreamParameters.streamId);
242
+ if (message instanceof ArrayBuffer) {
243
+ const view = new DataView(message);
244
+ const number = view.getUint32(0);
245
+ if (number == Math.pow(2, 32) - 1) {
246
+ logger.warn('dataChannelTest finished!');
247
+ this._nextDataChannelTestNumber = 0;
248
+ return;
249
+ }
250
+ if (number > this._nextDataChannelTestNumber) {
251
+ logger.warn('dataChannelTest: %s packets missing', number - this._nextDataChannelTestNumber);
252
+ }
253
+ this._nextDataChannelTestNumber = number + 1;
254
+ return;
255
+ }
256
+ else if (typeof message !== 'string') {
257
+ logger.warn('ignoring DataConsumer "message" (not a string)');
258
+ return;
259
+ }
260
+ switch (dataConsumer.label) {
261
+ case 'chat': {
262
+ const { peers } = store.getState();
263
+ const peersArray = Object.keys(peers).map((_peerId) => peers[_peerId]);
264
+ const sendingPeer = peersArray.find((peer) => peer.dataConsumers.includes(dataConsumer.id));
265
+ if (!sendingPeer) {
266
+ logger.warn('DataConsumer "message" from unknown peer');
267
+ break;
268
+ }
269
+ this._dispatch(requestActions.notify({
270
+ title: `${sendingPeer.displayName} says:`,
271
+ text: message,
272
+ timeout: 5000,
273
+ }));
274
+ break;
275
+ }
276
+ case 'bot': {
277
+ this._dispatch(requestActions.notify({
278
+ title: 'Message from Bot:',
279
+ text: message,
280
+ timeout: 5000,
281
+ }));
282
+ break;
283
+ }
284
+ }
285
+ });
286
+ this._dispatch(stateActions.addDataConsumer({
287
+ id: dataConsumer.id,
288
+ sctpStreamParameters: dataConsumer.sctpStreamParameters,
289
+ label: dataConsumer.label,
290
+ protocol: dataConsumer.protocol,
291
+ }, peerId));
292
+ accept();
293
+ }
294
+ catch (error) {
295
+ logger.error('"newDataConsumer" request failed:%o', error);
296
+ this._dispatch(requestActions.notify({
297
+ type: 'error',
298
+ text: `Error creating a DataConsumer: ${error}`,
299
+ }));
300
+ throw error;
301
+ }
302
+ });
303
+ break;
304
+ }
305
+ }
306
+ });
307
+ this._protoo.on('notification', (notification) => {
308
+ logger.debug('proto "notification" event [method:%s, data:%o]', notification.method, notification.data);
309
+ switch (notification.method) {
310
+ case 'mediasoupVersion': {
311
+ const { version } = notification.data;
312
+ this._dispatch(stateActions.setMediasoupVersion(version));
313
+ break;
314
+ }
315
+ case 'producerScore': {
316
+ const { producerId, score } = notification.data;
317
+ this._dispatch(stateActions.setProducerScore(producerId, score));
318
+ break;
319
+ }
320
+ case 'newPeer': {
321
+ const { peerId, displayName, device } = notification.data.peer;
322
+ const peer = {
323
+ id: peerId,
324
+ displayName,
325
+ device,
326
+ };
327
+ this._dispatch(stateActions.addPeer({ ...peer, consumers: [], dataConsumers: [] }));
328
+ this._dispatch(requestActions.notify({
329
+ text: `${peer.displayName} has joined the room`,
330
+ }));
331
+ break;
332
+ }
333
+ case 'peerClosed': {
334
+ const { peerId } = notification.data;
335
+ this._dispatch(stateActions.removePeer(peerId));
336
+ break;
337
+ }
338
+ case 'peerDisplayNameChanged': {
339
+ const { peerId, displayName, oldDisplayName } = notification.data;
340
+ this._dispatch(stateActions.setPeerDisplayName(displayName, peerId));
341
+ this._dispatch(requestActions.notify({
342
+ text: `${oldDisplayName} is now ${displayName}`,
343
+ }));
344
+ break;
345
+ }
346
+ case 'downlinkBwe': {
347
+ logger.debug("'downlinkBwe' event:%o", notification.data);
348
+ break;
349
+ }
350
+ case 'consumerClosed': {
351
+ this._consumingAwaitQueue.push(async () => {
352
+ const { consumerId } = notification.data;
353
+ const consumer = this._consumers.get(consumerId);
354
+ if (!consumer) {
355
+ logger.warn(`'consumerClosed' notification for unknown consumerId %o`, consumerId);
356
+ return;
357
+ }
358
+ consumer.close();
359
+ this._consumers.delete(consumerId);
360
+ const { peerId } = consumer.appData || {};
361
+ this._dispatch(stateActions.removeConsumer(consumerId, peerId));
362
+ });
363
+ break;
364
+ }
365
+ case 'consumerPaused': {
366
+ this._consumingAwaitQueue.push(async () => {
367
+ const { consumerId } = notification.data;
368
+ const consumer = this._consumers.get(consumerId);
369
+ if (!consumer) {
370
+ logger.warn(`'consumerPaused' notification for unknown consumerId %o`, consumerId);
371
+ return;
372
+ }
373
+ consumer.pause();
374
+ this._dispatch(stateActions.setConsumerPaused(consumerId, 'remote'));
375
+ });
376
+ break;
377
+ }
378
+ case 'consumerResumed': {
379
+ this._consumingAwaitQueue.push(async () => {
380
+ const { consumerId } = notification.data;
381
+ const consumer = this._consumers.get(consumerId);
382
+ if (!consumer) {
383
+ logger.warn(`'consumerResumed' notification for unknown consumerId %o`, consumerId);
384
+ return;
385
+ }
386
+ consumer.resume();
387
+ this._dispatch(stateActions.setConsumerResumed(consumerId, 'remote'));
388
+ });
389
+ break;
390
+ }
391
+ case 'consumerLayersChanged': {
392
+ this._consumingAwaitQueue.push(async () => {
393
+ const { consumerId, layers } = notification.data;
394
+ const consumer = this._consumers.get(consumerId);
395
+ if (!consumer) {
396
+ logger.warn(`'consumerLayersChanged' notification for unknown consumerId %o`, consumerId);
397
+ return;
398
+ }
399
+ this._dispatch(stateActions.setConsumerCurrentLayers(consumerId, layers?.spatialLayer, layers?.temporalLayer));
400
+ });
401
+ break;
402
+ }
403
+ case 'consumerScore': {
404
+ this._consumingAwaitQueue.push(async () => {
405
+ const { consumerId, score } = notification.data;
406
+ const consumer = this._consumers.get(consumerId);
407
+ if (!consumer) {
408
+ logger.warn(`'consumerScore' notification for unknown consumerId %o`, consumerId);
409
+ return;
410
+ }
411
+ this._dispatch(stateActions.setConsumerScore(consumerId, score));
412
+ });
413
+ break;
414
+ }
415
+ case 'dataConsumerClosed': {
416
+ this._consumingAwaitQueue.push(async () => {
417
+ const { dataConsumerId } = notification.data;
418
+ const dataConsumer = this._dataConsumers.get(dataConsumerId);
419
+ if (!dataConsumer) {
420
+ logger.warn(`'dataConsumerClosed' notification for unknown dataConsumerId %o`, dataConsumerId);
421
+ return;
422
+ }
423
+ dataConsumer.close();
424
+ this._dataConsumers.delete(dataConsumerId);
425
+ const { peerId } = dataConsumer.appData || {};
426
+ this._dispatch(stateActions.removeDataConsumer(dataConsumerId, peerId));
427
+ });
428
+ break;
429
+ }
430
+ case 'activeSpeaker': {
431
+ const { peerId } = notification.data;
432
+ this._dispatch(stateActions.setRoomActiveSpeaker(peerId));
433
+ break;
434
+ }
435
+ case 'speakingPeers': {
436
+ const { peerVolumes } = notification.data;
437
+ const peerIds = peerVolumes.map(({ peerId }) => peerId);
438
+ this._dispatch(stateActions.setRoomSpeakingPeers(peerIds));
439
+ break;
440
+ }
441
+ default: {
442
+ logger.error('unknown protoo notification.method "%s"', notification.method);
443
+ }
444
+ }
445
+ });
446
+ }
447
+ async enableMic() {
448
+ logger.debug('enableMic()');
449
+ if (this._micProducer)
450
+ return;
451
+ if (!this._mediasoupDevice.canProduce('audio')) {
452
+ logger.error('enableMic() | cannot produce audio');
453
+ return;
454
+ }
455
+ let track;
456
+ try {
457
+ if (!this._externalVideo) {
458
+ logger.debug('enableMic() | calling navigator.mediaDevices.getUserMedia()');
459
+ const stream = await navigator.mediaDevices.getUserMedia({
460
+ audio: true,
461
+ });
462
+ track = stream.getAudioTracks()[0];
463
+ }
464
+ else {
465
+ const stream = await this._getExternalVideoStream();
466
+ track = stream.getAudioTracks()[0].clone();
467
+ }
468
+ let codec;
469
+ let codecOptions = {
470
+ opusStereo: true,
471
+ opusDtx: true,
472
+ opusFec: true,
473
+ opusNack: true,
474
+ };
475
+ const headerExtensionOptions = {
476
+ absCaptureTime: true,
477
+ };
478
+ if (this._forcePCMA) {
479
+ codec = this._mediasoupDevice.rtpCapabilities.codecs.find((c) => c.mimeType.toLowerCase() === 'audio/pcma');
480
+ if (!codec) {
481
+ throw new Error('desired PCMA codec+configuration is not supported');
482
+ }
483
+ codecOptions = undefined;
484
+ }
485
+ this._micProducer = await this._sendTransport.produce({
486
+ track,
487
+ codecOptions,
488
+ headerExtensionOptions,
489
+ codec,
490
+ appData: {
491
+ source: 'audio',
492
+ },
493
+ });
494
+ if (this._e2eKey && e2e.isSupported() && this._micProducer.rtpSender) {
495
+ e2e.setupSenderTransform(this._micProducer.rtpSender);
496
+ }
497
+ this._dispatch(stateActions.addProducer({
498
+ id: this._micProducer.id,
499
+ paused: this._micProducer.paused,
500
+ track: this._micProducer.track,
501
+ rtpParameters: this._micProducer.rtpParameters,
502
+ codec: this._micProducer.rtpParameters.codecs[0].mimeType.split('/')[1],
503
+ }));
504
+ this._micProducer.on('transportclose', () => {
505
+ this._micProducer = null;
506
+ });
507
+ this._micProducer.on('trackended', () => {
508
+ this._dispatch(requestActions.notify({
509
+ type: 'error',
510
+ text: 'Microphone disconnected!',
511
+ }));
512
+ this.disableMic();
513
+ });
514
+ }
515
+ catch (error) {
516
+ logger.error('enableMic() | failed:%o', error);
517
+ this._dispatch(requestActions.notify({
518
+ type: 'error',
519
+ text: `Error enabling microphone: ${error}`,
520
+ }));
521
+ if (track)
522
+ track.stop();
523
+ }
524
+ }
525
+ disableMic() {
526
+ logger.debug('disableMic()');
527
+ if (!this._micProducer)
528
+ return;
529
+ this._micProducer.close();
530
+ this._dispatch(stateActions.removeProducer(this._micProducer.id));
531
+ this._protoo.notify('closeProducer', {
532
+ producerId: this._micProducer.id,
533
+ });
534
+ this._micProducer = null;
535
+ }
536
+ muteMic() {
537
+ logger.debug('muteMic()');
538
+ this._micProducer.pause();
539
+ this._protoo.notify('pauseProducer', {
540
+ producerId: this._micProducer.id,
541
+ });
542
+ this._dispatch(stateActions.setProducerPaused(this._micProducer.id));
543
+ }
544
+ unmuteMic() {
545
+ logger.debug('unmuteMic()');
546
+ this._micProducer.resume();
547
+ this._protoo.notify('resumeProducer', {
548
+ producerId: this._micProducer.id,
549
+ });
550
+ this._dispatch(stateActions.setProducerResumed(this._micProducer.id));
551
+ }
552
+ async enableWebcam() {
553
+ logger.debug('enableWebcam()');
554
+ if (this._webcamProducer) {
555
+ return;
556
+ }
557
+ else if (this._shareProducer) {
558
+ await this.disableShare();
559
+ }
560
+ if (!this._mediasoupDevice.canProduce('video')) {
561
+ logger.error('enableWebcam() | cannot produce video');
562
+ return;
563
+ }
564
+ let track;
565
+ let device;
566
+ this._dispatch(stateActions.setWebcamInProgress(true));
567
+ try {
568
+ if (!this._externalVideo) {
569
+ await this._updateWebcams();
570
+ device = this._webcam.device;
571
+ const { resolution } = this._webcam;
572
+ if (!device) {
573
+ throw new Error('no webcam devices');
574
+ }
575
+ logger.debug('enableWebcam() | calling navigator.mediaDevices.getUserMedia()');
576
+ const stream = await navigator.mediaDevices.getUserMedia({
577
+ video: {
578
+ deviceId: { ideal: device.deviceId },
579
+ ...WEBCAM_VIDEO_CONSTRAINS[resolution],
580
+ },
581
+ });
582
+ track = stream.getVideoTracks()[0];
583
+ }
584
+ else {
585
+ device = { label: 'external video' };
586
+ const stream = await this._getExternalVideoStream();
587
+ track = stream.getVideoTracks()[0].clone();
588
+ }
589
+ let encodings;
590
+ let codec;
591
+ const codecOptions = {
592
+ videoGoogleStartBitrate: 1000,
593
+ };
594
+ const headerExtensionOptions = {
595
+ absCaptureTime: true,
596
+ };
597
+ if (this._forceVP8) {
598
+ codec = this._mediasoupDevice.rtpCapabilities.codecs.find((c) => c.mimeType.toLowerCase() === 'video/vp8');
599
+ if (!codec) {
600
+ throw new Error('desired VP8 codec+configuration is not supported');
601
+ }
602
+ }
603
+ else if (this._forceH264) {
604
+ codec = this._mediasoupDevice.rtpCapabilities.codecs.find((c) => c.mimeType.toLowerCase() === 'video/h264');
605
+ if (!codec) {
606
+ throw new Error('desired H264 codec+configuration is not supported');
607
+ }
608
+ }
609
+ else if (this._forceVP9) {
610
+ codec = this._mediasoupDevice.rtpCapabilities.codecs.find((c) => c.mimeType.toLowerCase() === 'video/vp9');
611
+ if (!codec) {
612
+ throw new Error('desired VP9 codec+configuration is not supported');
613
+ }
614
+ }
615
+ else if (this._forceAV1) {
616
+ codec = this._mediasoupDevice.rtpCapabilities.codecs.find((c) => c.mimeType.toLowerCase() === 'video/av1');
617
+ if (!codec) {
618
+ throw new Error('desired AV1 codec+configuration is not supported');
619
+ }
620
+ }
621
+ if (this._enableWebcamLayers) {
622
+ const firstVideoCodec = this._mediasoupDevice.rtpCapabilities.codecs.find((c) => c.kind === 'video');
623
+ if (((this._forceVP9 || this._forceAV1) && codec) ||
624
+ ['video/vp9', 'video/av1'].includes(firstVideoCodec.mimeType.toLowerCase())) {
625
+ encodings = [
626
+ {
627
+ maxBitrate: 5000000,
628
+ scalabilityMode: this._webcamScalabilityMode || 'L3T3_KEY',
629
+ },
630
+ ];
631
+ }
632
+ else {
633
+ encodings = [
634
+ {
635
+ scaleResolutionDownBy: 1,
636
+ maxBitrate: 5000000,
637
+ scalabilityMode: this._webcamScalabilityMode || 'L1T3',
638
+ },
639
+ ];
640
+ if (this._numWebcamSimulcastStreams > 1) {
641
+ encodings.unshift({
642
+ scaleResolutionDownBy: 2,
643
+ maxBitrate: 1000000,
644
+ scalabilityMode: this._webcamScalabilityMode || 'L1T3',
645
+ });
646
+ }
647
+ if (this._numWebcamSimulcastStreams > 2) {
648
+ encodings.unshift({
649
+ scaleResolutionDownBy: 4,
650
+ maxBitrate: 500000,
651
+ scalabilityMode: this._webcamScalabilityMode || 'L1T3',
652
+ });
653
+ }
654
+ }
655
+ }
656
+ if (this._videoContentHint) {
657
+ logger.debug('enableWebcam() | applying track.contentHint = %o', this._videoContentHint);
658
+ track.contentHint = this._videoContentHint;
659
+ }
660
+ this._webcamProducer = await this._sendTransport.produce({
661
+ track,
662
+ encodings,
663
+ codecOptions,
664
+ headerExtensionOptions,
665
+ codec,
666
+ appData: {
667
+ source: 'video',
668
+ },
669
+ });
670
+ if (this._e2eKey && e2e.isSupported() && this._webcamProducer.rtpSender) {
671
+ e2e.setupSenderTransform(this._webcamProducer.rtpSender);
672
+ }
673
+ this._dispatch(stateActions.addProducer({
674
+ id: this._webcamProducer.id,
675
+ deviceLabel: device.label,
676
+ type: this._getWebcamType(device),
677
+ paused: this._webcamProducer.paused,
678
+ track: this._webcamProducer.track,
679
+ rtpParameters: this._webcamProducer.rtpParameters,
680
+ codec: this._webcamProducer.rtpParameters.codecs[0].mimeType.split('/')[1],
681
+ }));
682
+ this._webcamProducer.on('transportclose', () => {
683
+ this._webcamProducer = null;
684
+ });
685
+ this._webcamProducer.on('trackended', () => {
686
+ this._dispatch(requestActions.notify({
687
+ type: 'error',
688
+ text: 'Webcam disconnected!',
689
+ }));
690
+ this.disableWebcam();
691
+ });
692
+ }
693
+ catch (error) {
694
+ logger.error('enableWebcam() | failed:%o', error);
695
+ this._dispatch(requestActions.notify({
696
+ type: 'error',
697
+ text: `Error enabling webcam: ${error}`,
698
+ }));
699
+ if (track)
700
+ track.stop();
701
+ }
702
+ this._dispatch(stateActions.setWebcamInProgress(false));
703
+ }
704
+ disableWebcam() {
705
+ logger.debug('disableWebcam()');
706
+ if (!this._webcamProducer)
707
+ return;
708
+ this._webcamProducer.close();
709
+ this._dispatch(stateActions.removeProducer(this._webcamProducer.id));
710
+ this._protoo.notify('closeProducer', {
711
+ producerId: this._webcamProducer.id,
712
+ });
713
+ this._webcamProducer = null;
714
+ }
715
+ async changeWebcam() {
716
+ logger.debug('changeWebcam()');
717
+ this._dispatch(stateActions.setWebcamInProgress(true));
718
+ try {
719
+ await this._updateWebcams();
720
+ const array = Array.from(this._webcams.keys());
721
+ const len = array.length;
722
+ const deviceId = this._webcam.device
723
+ ? this._webcam.device.deviceId
724
+ : undefined;
725
+ let idx = array.indexOf(deviceId);
726
+ if (idx < len - 1)
727
+ idx++;
728
+ else
729
+ idx = 0;
730
+ this._webcam.device = this._webcams.get(array[idx]) ?? null;
731
+ logger.debug('changeWebcam() | new selected webcam [device:%o]', this._webcam.device);
732
+ this._webcam.resolution = 'hd';
733
+ if (!this._webcam.device)
734
+ throw new Error('no webcam devices');
735
+ this._webcamProducer.track.stop();
736
+ logger.debug('changeWebcam() | calling navigator.mediaDevices.getUserMedia()');
737
+ const stream = await navigator.mediaDevices.getUserMedia({
738
+ video: {
739
+ deviceId: { exact: this._webcam.device.deviceId },
740
+ ...WEBCAM_VIDEO_CONSTRAINS[this._webcam.resolution],
741
+ },
742
+ });
743
+ const track = stream.getVideoTracks()[0];
744
+ if (this._videoContentHint) {
745
+ logger.debug('changeWebcam() | applying track.contentHint = %o', this._videoContentHint);
746
+ track.contentHint = this._videoContentHint;
747
+ }
748
+ await this._webcamProducer.replaceTrack({ track });
749
+ this._dispatch(stateActions.setProducerTrack(this._webcamProducer.id, track));
750
+ }
751
+ catch (error) {
752
+ logger.error('changeWebcam() | failed: %o', error);
753
+ this._dispatch(requestActions.notify({
754
+ type: 'error',
755
+ text: `Could not change webcam: ${error}`,
756
+ }));
757
+ }
758
+ this._dispatch(stateActions.setWebcamInProgress(false));
759
+ }
760
+ async changeWebcamResolution() {
761
+ logger.debug('changeWebcamResolution()');
762
+ this._dispatch(stateActions.setWebcamInProgress(true));
763
+ try {
764
+ switch (this._webcam.resolution) {
765
+ case 'qvga':
766
+ this._webcam.resolution = 'vga';
767
+ break;
768
+ case 'vga':
769
+ this._webcam.resolution = 'hd';
770
+ break;
771
+ case 'hd':
772
+ this._webcam.resolution = 'qvga';
773
+ break;
774
+ default:
775
+ this._webcam.resolution = 'hd';
776
+ }
777
+ logger.debug('changeWebcamResolution() | calling navigator.mediaDevices.getUserMedia()');
778
+ const stream = await navigator.mediaDevices.getUserMedia({
779
+ video: {
780
+ deviceId: { exact: this._webcam.device.deviceId },
781
+ ...WEBCAM_VIDEO_CONSTRAINS[this._webcam.resolution],
782
+ },
783
+ });
784
+ const track = stream.getVideoTracks()[0];
785
+ if (this._videoContentHint) {
786
+ logger.debug('changeWebcamResolution() | applying track.contentHint = %o', this._videoContentHint);
787
+ track.contentHint = this._videoContentHint;
788
+ }
789
+ await this._webcamProducer.replaceTrack({ track });
790
+ this._dispatch(stateActions.setProducerTrack(this._webcamProducer.id, track));
791
+ }
792
+ catch (error) {
793
+ logger.error('changeWebcamResolution() | failed: %o', error);
794
+ this._dispatch(requestActions.notify({
795
+ type: 'error',
796
+ text: `Could not change webcam resolution: ${error}`,
797
+ }));
798
+ }
799
+ this._dispatch(stateActions.setWebcamInProgress(false));
800
+ }
801
+ async enableShare() {
802
+ logger.debug('enableShare()');
803
+ if (this._shareProducer)
804
+ return;
805
+ else if (this._webcamProducer)
806
+ await this.disableWebcam();
807
+ if (!this._mediasoupDevice.canProduce('video')) {
808
+ logger.error('enableShare() | cannot produce video');
809
+ return;
810
+ }
811
+ let track;
812
+ this._dispatch(stateActions.setShareInProgress(true));
813
+ try {
814
+ const resolution = this._screenSharing4K ? '4k' : 'hd';
815
+ logger.debug('enableShare() | calling navigator.mediaDevices.getDisplayMedia() with resolution %o', resolution);
816
+ const stream = await navigator.mediaDevices.getDisplayMedia({
817
+ audio: false,
818
+ video: {
819
+ displaySurface: 'monitor',
820
+ logicalSurface: true,
821
+ cursor: true,
822
+ ...SCREEN_SHARING_VIDEO_CONSTRAINS[resolution],
823
+ frameRate: { ideal: 30 },
824
+ },
825
+ });
826
+ if (!stream) {
827
+ this._dispatch(stateActions.setShareInProgress(true));
828
+ return;
829
+ }
830
+ track = stream.getVideoTracks()[0];
831
+ let encodings;
832
+ let codec;
833
+ const codecOptions = {
834
+ videoGoogleStartBitrate: 1000,
835
+ };
836
+ if (this._forceVP8) {
837
+ codec = this._mediasoupDevice.rtpCapabilities.codecs.find((c) => c.mimeType.toLowerCase() === 'video/vp8');
838
+ if (!codec) {
839
+ throw new Error('desired VP8 codec+configuration is not supported');
840
+ }
841
+ }
842
+ else if (this._forceH264) {
843
+ codec = this._mediasoupDevice.rtpCapabilities.codecs.find((c) => c.mimeType.toLowerCase() === 'video/h264');
844
+ if (!codec) {
845
+ throw new Error('desired H264 codec+configuration is not supported');
846
+ }
847
+ }
848
+ else if (this._forceVP9) {
849
+ codec = this._mediasoupDevice.rtpCapabilities.codecs.find((c) => c.mimeType.toLowerCase() === 'video/vp9');
850
+ if (!codec) {
851
+ throw new Error('desired VP9 codec+configuration is not supported');
852
+ }
853
+ }
854
+ else if (this._forceAV1) {
855
+ codec = this._mediasoupDevice.rtpCapabilities.codecs.find((c) => c.mimeType.toLowerCase() === 'video/av1');
856
+ if (!codec) {
857
+ throw new Error('desired AV1 codec+configuration is not supported');
858
+ }
859
+ }
860
+ if (this._enableSharingLayers) {
861
+ const firstVideoCodec = this._mediasoupDevice.rtpCapabilities.codecs.find((c) => c.kind === 'video');
862
+ if (((this._forceVP9 || this._forceAV1) && codec) ||
863
+ ['video/vp9', 'video/av1'].includes(firstVideoCodec.mimeType.toLowerCase())) {
864
+ encodings = [
865
+ {
866
+ maxBitrate: 5000000,
867
+ // @ts-ignore
868
+ scalabilityMode: this._sharingScalabilityMode || 'L3T3',
869
+ dtx: true,
870
+ },
871
+ ];
872
+ }
873
+ else {
874
+ encodings = [
875
+ {
876
+ scaleResolutionDownBy: 1,
877
+ maxBitrate: 5000000,
878
+ // @ts-ignore
879
+ scalabilityMode: this._sharingScalabilityMode || 'L1T3',
880
+ dtx: true,
881
+ },
882
+ ];
883
+ if (this._numSharingSimulcastStreams > 1) {
884
+ encodings.unshift({
885
+ scaleResolutionDownBy: 2,
886
+ maxBitrate: 1000000,
887
+ // @ts-ignore
888
+ scalabilityMode: this._sharingScalabilityMode || 'L1T3',
889
+ dtx: true,
890
+ });
891
+ }
892
+ if (this._numSharingSimulcastStreams > 2) {
893
+ encodings.unshift({
894
+ scaleResolutionDownBy: 4,
895
+ maxBitrate: 500000,
896
+ // @ts-ignore
897
+ scalabilityMode: this._sharingScalabilityMode || 'L1T3',
898
+ dtx: true,
899
+ });
900
+ }
901
+ }
902
+ }
903
+ else {
904
+ encodings = [
905
+ {
906
+ scaleResolutionDownBy: 1,
907
+ maxBitrate: 5000000,
908
+ // @ts-ignore
909
+ dtx: true,
910
+ },
911
+ ];
912
+ }
913
+ if (this._videoContentHint) {
914
+ logger.debug('enableShare() | applying track.contentHint = %o', this._videoContentHint);
915
+ track.contentHint = this._videoContentHint;
916
+ }
917
+ this._shareProducer = await this._sendTransport.produce({
918
+ track,
919
+ encodings,
920
+ codecOptions,
921
+ codec,
922
+ appData: {
923
+ source: 'screensharing',
924
+ },
925
+ });
926
+ if (this._e2eKey && e2e.isSupported() && this._shareProducer.rtpSender) {
927
+ e2e.setupSenderTransform(this._shareProducer.rtpSender);
928
+ }
929
+ this._dispatch(stateActions.addProducer({
930
+ id: this._shareProducer.id,
931
+ type: 'share',
932
+ paused: this._shareProducer.paused,
933
+ track: this._shareProducer.track,
934
+ rtpParameters: this._shareProducer.rtpParameters,
935
+ codec: this._shareProducer.rtpParameters.codecs[0].mimeType.split('/')[1],
936
+ }));
937
+ this._shareProducer.on('transportclose', () => {
938
+ this._shareProducer = null;
939
+ });
940
+ this._shareProducer.on('trackended', () => {
941
+ this._dispatch(requestActions.notify({
942
+ type: 'error',
943
+ text: 'Share disconnected!',
944
+ }));
945
+ this.disableShare();
946
+ });
947
+ }
948
+ catch (error) {
949
+ logger.error('enableShare() | failed:%o', error);
950
+ if (error.name !== 'NotAllowedError') {
951
+ this._dispatch(requestActions.notify({
952
+ type: 'error',
953
+ text: `Error sharing: ${error}`,
954
+ }));
955
+ }
956
+ if (track)
957
+ track.stop();
958
+ }
959
+ this._dispatch(stateActions.setShareInProgress(false));
960
+ }
961
+ disableShare() {
962
+ logger.debug('disableShare()');
963
+ if (!this._shareProducer)
964
+ return;
965
+ this._shareProducer.close();
966
+ this._dispatch(stateActions.removeProducer(this._shareProducer.id));
967
+ this._protoo.notify('closeProducer', {
968
+ producerId: this._shareProducer.id,
969
+ });
970
+ this._shareProducer = null;
971
+ }
972
+ enableAudioOnly() {
973
+ logger.debug('enableAudioOnly()');
974
+ this._dispatch(stateActions.setAudioOnlyInProgress(true));
975
+ this.disableWebcam();
976
+ for (const consumer of this._consumers.values()) {
977
+ if (consumer.kind !== 'video')
978
+ continue;
979
+ this._pauseConsumer(consumer);
980
+ }
981
+ this._dispatch(stateActions.setAudioOnlyState(true));
982
+ this._dispatch(stateActions.setAudioOnlyInProgress(false));
983
+ }
984
+ disableAudioOnly() {
985
+ logger.debug('disableAudioOnly()');
986
+ this._dispatch(stateActions.setAudioOnlyInProgress(true));
987
+ if (!this._webcamProducer &&
988
+ this._produce &&
989
+ (cookiesManager.getDevices() || {}).webcamEnabled) {
990
+ this.enableWebcam();
991
+ }
992
+ for (const consumer of this._consumers.values()) {
993
+ if (consumer.kind !== 'video')
994
+ continue;
995
+ this._resumeConsumer(consumer);
996
+ }
997
+ this._dispatch(stateActions.setAudioOnlyState(false));
998
+ this._dispatch(stateActions.setAudioOnlyInProgress(false));
999
+ }
1000
+ muteAudio() {
1001
+ logger.debug('muteAudio()');
1002
+ this._dispatch(stateActions.setAudioMutedState(true));
1003
+ }
1004
+ unmuteAudio() {
1005
+ logger.debug('unmuteAudio()');
1006
+ this._dispatch(stateActions.setAudioMutedState(false));
1007
+ }
1008
+ async restartIce() {
1009
+ logger.debug('restartIce()');
1010
+ this._dispatch(stateActions.setRestartIceInProgress(true));
1011
+ try {
1012
+ if (this._sendTransport) {
1013
+ const { iceParameters } = await this._protoo.request('restartIce', {
1014
+ transportId: this._sendTransport.id,
1015
+ });
1016
+ await this._sendTransport.restartIce({ iceParameters });
1017
+ }
1018
+ if (this._recvTransport) {
1019
+ const { iceParameters } = await this._protoo.request('restartIce', {
1020
+ transportId: this._recvTransport.id,
1021
+ });
1022
+ await this._recvTransport.restartIce({ iceParameters });
1023
+ }
1024
+ this._dispatch(requestActions.notify({
1025
+ text: 'ICE restarted',
1026
+ }));
1027
+ }
1028
+ catch (error) {
1029
+ logger.error('restartIce() | failed:%o', error);
1030
+ this._dispatch(requestActions.notify({
1031
+ type: 'error',
1032
+ text: `ICE restart failed: ${error}`,
1033
+ }));
1034
+ }
1035
+ this._dispatch(stateActions.setRestartIceInProgress(false));
1036
+ }
1037
+ async setMaxSendingSpatialLayer(spatialLayer) {
1038
+ logger.debug('setMaxSendingSpatialLayer() [spatialLayer:%s]', spatialLayer);
1039
+ try {
1040
+ if (this._webcamProducer)
1041
+ await this._webcamProducer.setMaxSpatialLayer(spatialLayer);
1042
+ else if (this._shareProducer)
1043
+ await this._shareProducer.setMaxSpatialLayer(spatialLayer);
1044
+ }
1045
+ catch (error) {
1046
+ logger.error('setMaxSendingSpatialLayer() | failed:%o', error);
1047
+ this._dispatch(requestActions.notify({
1048
+ type: 'error',
1049
+ text: `Error setting max sending video spatial layer: ${error}`,
1050
+ }));
1051
+ }
1052
+ }
1053
+ setConsumerPreferredLayers(consumerId, spatialLayer, temporalLayer) {
1054
+ logger.debug('setConsumerPreferredLayers() [consumerId:%s, spatialLayer:%s, temporalLayer:%s]', consumerId, spatialLayer, temporalLayer);
1055
+ this._protoo.notify('setConsumerPreferredLayers', {
1056
+ consumerId,
1057
+ spatialLayer,
1058
+ temporalLayer,
1059
+ });
1060
+ this._dispatch(stateActions.setConsumerPreferredLayers(consumerId, spatialLayer, temporalLayer));
1061
+ }
1062
+ setConsumerPriority(consumerId, priority) {
1063
+ logger.debug('setConsumerPriority() [consumerId:%s, priority:%d]', consumerId, priority);
1064
+ this._protoo.notify('setConsumerPriority', {
1065
+ consumerId,
1066
+ priority,
1067
+ });
1068
+ this._dispatch(stateActions.setConsumerPriority(consumerId, priority));
1069
+ }
1070
+ requestConsumerKeyFrame(consumerId) {
1071
+ logger.debug('requestConsumerKeyFrame() [consumerId:%s]', consumerId);
1072
+ this._protoo.notify('requestConsumerKeyFrame', { consumerId });
1073
+ this._dispatch(requestActions.notify({
1074
+ text: 'Keyframe requested for video consumer',
1075
+ }));
1076
+ }
1077
+ async enableChatDataProducer() {
1078
+ logger.debug('enableChatDataProducer()');
1079
+ try {
1080
+ this._chatDataProducer = await this._sendTransport.produceData({
1081
+ ordered: true,
1082
+ label: 'chat',
1083
+ // @ts-ignore
1084
+ priority: 'medium',
1085
+ appData: { channel: 'chat' },
1086
+ });
1087
+ this._dispatch(stateActions.addDataProducer({
1088
+ id: this._chatDataProducer.id,
1089
+ sctpStreamParameters: this._chatDataProducer.sctpStreamParameters,
1090
+ label: this._chatDataProducer.label,
1091
+ protocol: this._chatDataProducer.protocol,
1092
+ }));
1093
+ this._chatDataProducer.on('transportclose', () => {
1094
+ this._chatDataProducer = null;
1095
+ });
1096
+ this._chatDataProducer.on('open', () => {
1097
+ logger.debug('chat DataProducer "open" event');
1098
+ });
1099
+ this._chatDataProducer.on('close', () => {
1100
+ logger.error('chat DataProducer "close" event');
1101
+ this._chatDataProducer = null;
1102
+ this._dispatch(requestActions.notify({
1103
+ type: 'error',
1104
+ text: 'Chat DataProducer closed',
1105
+ }));
1106
+ });
1107
+ this._chatDataProducer.on('error', (error) => {
1108
+ logger.error('chat DataProducer "error" event:%o', error);
1109
+ this._dispatch(requestActions.notify({
1110
+ type: 'error',
1111
+ text: `Chat DataProducer error: ${error}`,
1112
+ }));
1113
+ });
1114
+ this._chatDataProducer.on('bufferedamountlow', () => {
1115
+ logger.debug('chat DataProducer "bufferedamountlow" event');
1116
+ });
1117
+ }
1118
+ catch (error) {
1119
+ logger.error('enableChatDataProducer() | failed:%o', error);
1120
+ this._dispatch(requestActions.notify({
1121
+ type: 'error',
1122
+ text: `Error enabling chat DataProducer: ${error}`,
1123
+ }));
1124
+ throw error;
1125
+ }
1126
+ }
1127
+ async enableBotDataProducer() {
1128
+ logger.debug('enableBotDataProducer()');
1129
+ try {
1130
+ this._botDataProducer = await this._sendTransport.produceData({
1131
+ ordered: true,
1132
+ label: 'bot',
1133
+ // @ts-ignore
1134
+ priority: 'medium',
1135
+ appData: { channel: 'bot' },
1136
+ });
1137
+ this._dispatch(stateActions.addDataProducer({
1138
+ id: this._botDataProducer.id,
1139
+ sctpStreamParameters: this._botDataProducer.sctpStreamParameters,
1140
+ label: this._botDataProducer.label,
1141
+ protocol: this._botDataProducer.protocol,
1142
+ }));
1143
+ this._botDataProducer.on('transportclose', () => {
1144
+ this._botDataProducer = null;
1145
+ });
1146
+ this._botDataProducer.on('open', () => {
1147
+ logger.debug('bot DataProducer "open" event');
1148
+ });
1149
+ this._botDataProducer.on('close', () => {
1150
+ logger.error('bot DataProducer "close" event');
1151
+ this._botDataProducer = null;
1152
+ this._dispatch(requestActions.notify({
1153
+ type: 'error',
1154
+ text: 'Bot DataProducer closed',
1155
+ }));
1156
+ });
1157
+ this._botDataProducer.on('error', (error) => {
1158
+ logger.error('bot DataProducer "error" event:%o', error);
1159
+ this._dispatch(requestActions.notify({
1160
+ type: 'error',
1161
+ text: `Bot DataProducer error: ${error}`,
1162
+ }));
1163
+ });
1164
+ this._botDataProducer.on('bufferedamountlow', () => {
1165
+ logger.debug('bot DataProducer "bufferedamountlow" event');
1166
+ });
1167
+ }
1168
+ catch (error) {
1169
+ logger.error('enableBotDataProducer() | failed:%o', error);
1170
+ this._dispatch(requestActions.notify({
1171
+ type: 'error',
1172
+ text: `Error enabling bot DataProducer: ${error}`,
1173
+ }));
1174
+ throw error;
1175
+ }
1176
+ }
1177
+ async sendChatMessage(text) {
1178
+ logger.debug('sendChatMessage() [text:"%s]', text);
1179
+ if (!this._chatDataProducer) {
1180
+ this._dispatch(requestActions.notify({
1181
+ type: 'error',
1182
+ text: 'No chat DataProducer',
1183
+ }));
1184
+ return;
1185
+ }
1186
+ try {
1187
+ this._chatDataProducer.send(text);
1188
+ }
1189
+ catch (error) {
1190
+ logger.error('chat DataProducer.send() failed:%o', error);
1191
+ this._dispatch(requestActions.notify({
1192
+ type: 'error',
1193
+ text: `chat DataProducer.send() failed: ${error}`,
1194
+ }));
1195
+ }
1196
+ }
1197
+ async sendBotMessage(text) {
1198
+ logger.debug('sendBotMessage() [text:"%s]', text);
1199
+ if (!this._botDataProducer) {
1200
+ this._dispatch(requestActions.notify({
1201
+ type: 'error',
1202
+ text: 'No bot DataProducer',
1203
+ }));
1204
+ return;
1205
+ }
1206
+ try {
1207
+ this._botDataProducer.send(text);
1208
+ }
1209
+ catch (error) {
1210
+ logger.error('bot DataProducer.send() failed:%o', error);
1211
+ this._dispatch(requestActions.notify({
1212
+ type: 'error',
1213
+ text: `bot DataProducer.send() failed: ${error}`,
1214
+ }));
1215
+ }
1216
+ }
1217
+ changeDisplayName(displayName) {
1218
+ logger.debug('changeDisplayName() [displayName:"%s"]', displayName);
1219
+ cookiesManager.setUser({ displayName });
1220
+ this._protoo.notify('changeDisplayName', { displayName });
1221
+ this._displayName = displayName;
1222
+ this._dispatch(stateActions.setDisplayName(displayName));
1223
+ this._dispatch(requestActions.notify({
1224
+ text: 'Display name changed',
1225
+ }));
1226
+ }
1227
+ async getSendTransportRemoteStats() {
1228
+ logger.debug('getSendTransportRemoteStats()');
1229
+ if (!this._sendTransport)
1230
+ return;
1231
+ const { stats } = await this._protoo.request('getTransportStats', {
1232
+ transportId: this._sendTransport.id,
1233
+ });
1234
+ return stats;
1235
+ }
1236
+ async getRecvTransportRemoteStats() {
1237
+ logger.debug('getRecvTransportRemoteStats()');
1238
+ if (!this._recvTransport)
1239
+ return;
1240
+ const { stats } = await this._protoo.request('getTransportStats', {
1241
+ transportId: this._recvTransport.id,
1242
+ });
1243
+ return stats;
1244
+ }
1245
+ async getAudioRemoteStats() {
1246
+ logger.debug('getAudioRemoteStats()');
1247
+ if (!this._micProducer)
1248
+ return;
1249
+ const { stats } = await this._protoo.request('getProducerStats', {
1250
+ producerId: this._micProducer.id,
1251
+ });
1252
+ return stats;
1253
+ }
1254
+ async getVideoRemoteStats() {
1255
+ logger.debug('getVideoRemoteStats()');
1256
+ const producer = this._webcamProducer || this._shareProducer;
1257
+ if (!producer)
1258
+ return;
1259
+ const { stats } = await this._protoo.request('getProducerStats', {
1260
+ producerId: producer.id,
1261
+ });
1262
+ return stats;
1263
+ }
1264
+ async getConsumerRemoteStats(consumerId) {
1265
+ logger.debug('getConsumerRemoteStats()');
1266
+ const consumer = this._consumers.get(consumerId);
1267
+ if (!consumer)
1268
+ return;
1269
+ const { stats } = await this._protoo.request('getConsumerStats', {
1270
+ consumerId,
1271
+ });
1272
+ return stats;
1273
+ }
1274
+ async getChatDataProducerRemoteStats() {
1275
+ logger.debug('getChatDataProducerRemoteStats()');
1276
+ const dataProducer = this._chatDataProducer;
1277
+ if (!dataProducer)
1278
+ return;
1279
+ const { stats } = await this._protoo.request('getDataProducerStats', {
1280
+ dataProducerId: dataProducer.id,
1281
+ });
1282
+ return stats;
1283
+ }
1284
+ async getBotDataProducerRemoteStats() {
1285
+ logger.debug('getBotDataProducerRemoteStats()');
1286
+ const dataProducer = this._botDataProducer;
1287
+ if (!dataProducer)
1288
+ return;
1289
+ const { stats } = await this._protoo.request('getDataProducerStats', {
1290
+ dataProducerId: dataProducer.id,
1291
+ });
1292
+ return stats;
1293
+ }
1294
+ async getDataConsumerRemoteStats(dataConsumerId) {
1295
+ logger.debug('getDataConsumerRemoteStats()');
1296
+ const dataConsumer = this._dataConsumers.get(dataConsumerId);
1297
+ if (!dataConsumer)
1298
+ return;
1299
+ const { stats } = await this._protoo.request('getDataConsumerStats', {
1300
+ dataConsumerId,
1301
+ });
1302
+ return stats;
1303
+ }
1304
+ async getSendTransportLocalStats() {
1305
+ logger.debug('getSendTransportLocalStats()');
1306
+ if (!this._sendTransport)
1307
+ return;
1308
+ return this._sendTransport.getStats();
1309
+ }
1310
+ async getRecvTransportLocalStats() {
1311
+ logger.debug('getRecvTransportLocalStats()');
1312
+ if (!this._recvTransport)
1313
+ return;
1314
+ return this._recvTransport.getStats();
1315
+ }
1316
+ async getAudioLocalStats() {
1317
+ logger.debug('getAudioLocalStats()');
1318
+ if (!this._micProducer)
1319
+ return;
1320
+ return this._micProducer.getStats();
1321
+ }
1322
+ async getVideoLocalStats() {
1323
+ logger.debug('getVideoLocalStats()');
1324
+ const producer = this._webcamProducer || this._shareProducer;
1325
+ if (!producer)
1326
+ return;
1327
+ return producer.getStats();
1328
+ }
1329
+ async getConsumerLocalStats(consumerId) {
1330
+ const consumer = this._consumers.get(consumerId);
1331
+ if (!consumer)
1332
+ return;
1333
+ return consumer.getStats();
1334
+ }
1335
+ async applyNetworkThrottle({ secret, up, down, rtt, packetLoss, localhost }) {
1336
+ logger.debug('applyNetworkThrottle() [up:%s, down:%s, rtt:%s, packetLoss:%s, localhost:%s]', up, down, rtt, packetLoss, localhost);
1337
+ try {
1338
+ await this._protoo.request('applyNetworkThrottle', {
1339
+ secret,
1340
+ options: {
1341
+ up,
1342
+ down,
1343
+ rtt,
1344
+ packetLoss,
1345
+ localhost,
1346
+ },
1347
+ });
1348
+ }
1349
+ catch (error) {
1350
+ logger.error('applyNetworkThrottle() | failed:%o', error);
1351
+ this._dispatch(requestActions.notify({
1352
+ type: 'error',
1353
+ text: `Error applying network throttle: ${error}`,
1354
+ }));
1355
+ }
1356
+ }
1357
+ async stopNetworkThrottle({ silent = false, secret }) {
1358
+ logger.debug('stopNetworkThrottle()');
1359
+ try {
1360
+ await this._protoo.request('stopNetworkThrottle', { secret });
1361
+ }
1362
+ catch (error) {
1363
+ if (!silent) {
1364
+ logger.error('stopNetworkThrottle() | failed:%o', error);
1365
+ this._dispatch(requestActions.notify({
1366
+ type: 'error',
1367
+ text: `Error resetting network throttle: ${error}`,
1368
+ }));
1369
+ }
1370
+ }
1371
+ }
1372
+ async _joinRoom() {
1373
+ logger.debug('_joinRoom()');
1374
+ try {
1375
+ logger.debug('_joinRoom() | using mediasoupClient.Device.factory()');
1376
+ this._mediasoupDevice = await mediasoupClient.Device.factory({
1377
+ // @ts-ignore
1378
+ handlerName: this._handlerName,
1379
+ });
1380
+ this._dispatch(stateActions.setRoomMediasoupClientHandler(this._mediasoupDevice.handlerName));
1381
+ const { routerRtpCapabilities } = await this._protoo.request('getRouterRtpCapabilities');
1382
+ await this._mediasoupDevice.load({
1383
+ routerRtpCapabilities,
1384
+ preferLocalCodecsOrder: this._preferLocalCodecsOrder,
1385
+ });
1386
+ //
1387
+ {
1388
+ const stream = await navigator.mediaDevices.getUserMedia({
1389
+ audio: true,
1390
+ });
1391
+ const audioTrack = stream.getAudioTracks()[0];
1392
+ audioTrack.enabled = false;
1393
+ setTimeout(() => audioTrack.stop(), 120000);
1394
+ }
1395
+ if (this._produce) {
1396
+ const transportInfo = await this._protoo.request('createWebRtcTransport', {
1397
+ sctpCapabilities: this._useDataChannel
1398
+ ? this._mediasoupDevice.sctpCapabilities
1399
+ : undefined,
1400
+ forceTcp: this._forceTcp,
1401
+ appData: {
1402
+ direction: 'producer',
1403
+ },
1404
+ });
1405
+ const { transportId, iceParameters, iceCandidates, dtlsParameters, sctpParameters, } = transportInfo;
1406
+ this._sendTransport = this._mediasoupDevice.createSendTransport({
1407
+ id: transportId,
1408
+ iceParameters,
1409
+ iceCandidates,
1410
+ dtlsParameters: {
1411
+ ...dtlsParameters,
1412
+ role: 'auto',
1413
+ },
1414
+ sctpParameters,
1415
+ iceServers: [],
1416
+ proprietaryConstraints: PC_PROPRIETARY_CONSTRAINTS,
1417
+ additionalSettings: {
1418
+ // @ts-ignore
1419
+ encodedInsertableStreams: this._e2eKey && e2e.isSupported(),
1420
+ },
1421
+ });
1422
+ this._sendTransport.on('connect', ({ dtlsParameters: dtlsParameters2 }, callback, errback) => {
1423
+ this._protoo
1424
+ .request('connectWebRtcTransport', {
1425
+ transportId: this._sendTransport.id,
1426
+ dtlsParameters: dtlsParameters2,
1427
+ })
1428
+ .then(callback)
1429
+ .catch(errback);
1430
+ });
1431
+ this._sendTransport.on('produce', async ({ kind, rtpParameters, appData }, callback, errback) => {
1432
+ try {
1433
+ const { producerId } = await this._protoo.request('produce', {
1434
+ transportId: this._sendTransport.id,
1435
+ kind,
1436
+ rtpParameters,
1437
+ appData,
1438
+ });
1439
+ callback({ id: producerId });
1440
+ }
1441
+ catch (error) {
1442
+ errback(error);
1443
+ }
1444
+ });
1445
+ this._sendTransport.on('producedata', async ({ sctpStreamParameters, label, protocol, appData }, callback, errback) => {
1446
+ logger.debug('"producedata" event: [sctpStreamParameters:%o, appData:%o]', sctpStreamParameters, appData);
1447
+ try {
1448
+ const { dataProducerId } = await this._protoo.request('produceData', {
1449
+ transportId: this._sendTransport.id,
1450
+ sctpStreamParameters,
1451
+ label,
1452
+ protocol,
1453
+ appData,
1454
+ });
1455
+ callback({ id: dataProducerId });
1456
+ }
1457
+ catch (error) {
1458
+ errback(error);
1459
+ }
1460
+ });
1461
+ }
1462
+ if (this._consume) {
1463
+ const transportInfo = await this._protoo.request('createWebRtcTransport', {
1464
+ sctpCapabilities: this._useDataChannel
1465
+ ? this._mediasoupDevice.sctpCapabilities
1466
+ : undefined,
1467
+ forceTcp: this._forceTcp,
1468
+ appData: {
1469
+ direction: 'consumer',
1470
+ },
1471
+ });
1472
+ const { transportId, iceParameters, iceCandidates, dtlsParameters, sctpParameters, } = transportInfo;
1473
+ this._recvTransport = this._mediasoupDevice.createRecvTransport({
1474
+ id: transportId,
1475
+ iceParameters,
1476
+ iceCandidates,
1477
+ dtlsParameters: {
1478
+ ...dtlsParameters,
1479
+ role: 'auto',
1480
+ },
1481
+ sctpParameters,
1482
+ iceServers: [],
1483
+ additionalSettings: {
1484
+ // @ts-ignore
1485
+ encodedInsertableStreams: this._e2eKey && e2e.isSupported(),
1486
+ },
1487
+ });
1488
+ this._recvTransport.on('connect', ({ dtlsParameters: dtlsParameters2 }, callback, errback) => {
1489
+ this._protoo
1490
+ .request('connectWebRtcTransport', {
1491
+ transportId: this._recvTransport.id,
1492
+ dtlsParameters: dtlsParameters2,
1493
+ })
1494
+ .then(callback)
1495
+ .catch(errback);
1496
+ });
1497
+ }
1498
+ const { peers } = await this._protoo.request('join', {
1499
+ displayName: this._displayName,
1500
+ device: this._device,
1501
+ rtpCapabilities: this._consume
1502
+ ? this._mediasoupDevice.rtpCapabilities
1503
+ : undefined,
1504
+ sctpCapabilities: this._useDataChannel && this._consume
1505
+ ? this._mediasoupDevice.sctpCapabilities
1506
+ : undefined,
1507
+ });
1508
+ this._dispatch(stateActions.setRoomState('connected'));
1509
+ this._dispatch(stateActions.removeAllNotifications());
1510
+ this._dispatch(requestActions.notify({
1511
+ text: 'You are in the room!',
1512
+ timeout: 3000,
1513
+ }));
1514
+ for (const serializedPeer of peers) {
1515
+ const { peerId, displayName, device } = serializedPeer;
1516
+ const peer = {
1517
+ id: peerId,
1518
+ displayName,
1519
+ device,
1520
+ };
1521
+ this._dispatch(stateActions.addPeer({ ...peer, consumers: [], dataConsumers: [] }));
1522
+ }
1523
+ if (this._produce) {
1524
+ this._dispatch(stateActions.setMediaCapabilities({
1525
+ canSendMic: this._mediasoupDevice.canProduce('audio'),
1526
+ canSendWebcam: this._mediasoupDevice.canProduce('video'),
1527
+ }));
1528
+ if (this._useMic) {
1529
+ this.enableMic();
1530
+ }
1531
+ const devicesCookie = cookiesManager.getDevices();
1532
+ if (this._useWebcam ||
1533
+ (this._useWebcam === undefined &&
1534
+ (!devicesCookie || devicesCookie.webcamEnabled)) ||
1535
+ this._externalVideo) {
1536
+ this.enableWebcam();
1537
+ }
1538
+ if (this._useDataChannel) {
1539
+ this.enableChatDataProducer();
1540
+ this.enableBotDataProducer();
1541
+ }
1542
+ }
1543
+ if (this._stats) {
1544
+ const { me } = store.getState();
1545
+ this._dispatch(stateActions.setRoomStatsPeerId(me.id));
1546
+ }
1547
+ }
1548
+ catch (error) {
1549
+ logger.error('_joinRoom() failed:%o', error);
1550
+ this._dispatch(requestActions.notify({
1551
+ type: 'error',
1552
+ text: `Could not join the room: ${error}`,
1553
+ }));
1554
+ this.close();
1555
+ }
1556
+ }
1557
+ async _updateWebcams() {
1558
+ logger.debug('_updateWebcams()');
1559
+ this._webcams = new Map();
1560
+ logger.debug('_updateWebcams() | calling navigator.mediaDevices.enumerateDevices()');
1561
+ const devices = await navigator.mediaDevices.enumerateDevices();
1562
+ for (const device of devices) {
1563
+ if (device.kind !== 'videoinput')
1564
+ continue;
1565
+ this._webcams.set(device.deviceId, device);
1566
+ }
1567
+ const array = Array.from(this._webcams.values());
1568
+ const len = array.length;
1569
+ const currentWebcamId = this._webcam.device
1570
+ ? this._webcam.device.deviceId
1571
+ : undefined;
1572
+ logger.debug('_updateWebcams() [webcams:%o]', array);
1573
+ if (len === 0)
1574
+ this._webcam.device = null;
1575
+ else if (!this._webcams.has(currentWebcamId))
1576
+ this._webcam.device = array[0];
1577
+ this._dispatch(stateActions.setCanChangeWebcam(this._webcams.size > 1));
1578
+ }
1579
+ _getWebcamType(device) {
1580
+ if (/(back|rear)/i.test(device.label)) {
1581
+ logger.debug('_getWebcamType() | it seems to be a back camera');
1582
+ return 'back';
1583
+ }
1584
+ else {
1585
+ logger.debug('_getWebcamType() | it seems to be a front camera');
1586
+ return 'front';
1587
+ }
1588
+ }
1589
+ _pauseConsumer(consumer) {
1590
+ if (consumer.paused)
1591
+ return;
1592
+ this._protoo.notify('pauseConsumer', { consumerId: consumer.id });
1593
+ consumer.pause();
1594
+ this._dispatch(stateActions.setConsumerPaused(consumer.id, 'local'));
1595
+ }
1596
+ async _resumeConsumer(consumer) {
1597
+ if (!consumer.paused)
1598
+ return;
1599
+ this._protoo.notify('resumeConsumer', { consumerId: consumer.id });
1600
+ consumer.resume();
1601
+ this._dispatch(stateActions.setConsumerResumed(consumer.id, 'local'));
1602
+ }
1603
+ async _getExternalVideoStream() {
1604
+ if (this._externalVideoStream)
1605
+ return this._externalVideoStream;
1606
+ if (this._externalVideo.readyState < 3) {
1607
+ await new Promise(resolve =>
1608
+ // @ts-ignore
1609
+ this._externalVideo.addEventListener('canplay', resolve));
1610
+ }
1611
+ if (this._externalVideo.captureStream)
1612
+ this._externalVideoStream = this._externalVideo.captureStream();
1613
+ else if (this._externalVideo.mozCaptureStream)
1614
+ this._externalVideoStream = this._externalVideo.mozCaptureStream();
1615
+ else
1616
+ throw new Error('video.captureStream() not supported');
1617
+ return this._externalVideoStream;
1618
+ }
1619
+ }
1620
+ //# sourceMappingURL=RoomClient.js.map