@livedigital/client 3.0.7 → 3.0.8

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.
@@ -1,5 +1,5 @@
1
- import { SocketIOEvents } from '../constants/events';
2
1
  import { Role } from './common';
2
+ import { SocketIOEvents } from './socket';
3
3
  export declare type GetNodeRequest = {
4
4
  channelId: string;
5
5
  role: Role;
@@ -0,0 +1,13 @@
1
+ export declare enum SocketIOEvents {
2
+ Connect = "connect",
3
+ Connected = "connected",
4
+ ConnectError = "connect_error",
5
+ Reconnect = "reconnect",
6
+ Reconnecting = "reconnecting",
7
+ Reconnected = "reconnected",
8
+ ReconnectAttempt = "reconnect_attempt",
9
+ Disconnect = "disconnect",
10
+ Disconnected = "disconnected",
11
+ Error = "error",
12
+ State = "state"
13
+ }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@livedigital/client",
3
3
  "author": "vlprojects",
4
4
  "license": "MIT",
5
- "version": "3.0.7",
5
+ "version": "3.0.8",
6
6
  "private": false,
7
7
  "bugs": {
8
8
  "url": "https://github.com/vlprojects/livedigital-sdk/issues"
@@ -23,7 +23,7 @@
23
23
  "url": "https://github.com/VLprojects/livedigital-sdk.git"
24
24
  },
25
25
  "scripts": {
26
- "build": "rm -rf dist && rollup -c && tsc --importHelpers",
26
+ "build": "rm -rf dist && rollup -c && tsc --importHelpers && cp src/constants/* dist/constants/",
27
27
  "lint": "eslint ./src",
28
28
  "test": "NODE_ENV=test mocha --config test/utils/runners/mocha/.mocharc.js"
29
29
  },
@@ -46,7 +46,7 @@
46
46
  "axios": "^0.21.4",
47
47
  "bowser": "^2.11.0",
48
48
  "debug": "^4.3.4",
49
- "mediasoup-client": "^3.6.50",
49
+ "mediasoup-client": "^3.6.103",
50
50
  "qs": "^6.11.0",
51
51
  "serialize-error": "^7.0.1",
52
52
  "socket.io-client": "^4.7.2",
@@ -53,15 +53,6 @@ export const PEER_EVENTS = {
53
53
  appDataUpdated: 'app-data-updated',
54
54
  };
55
55
 
56
- export enum SocketIOEvents {
57
- Connected = 'connected',
58
- Reconnecting = 'reconnecting',
59
- Reconnected = 'reconnected',
60
- Disconnected = 'disconnected',
61
- Reconnect = 'reconnect',
62
- Error = 'error',
63
- }
64
-
65
56
  export const MEDIASOUP_EVENTS = {
66
57
  newProducer: 'peer.newProducer',
67
58
  producerClose: 'producer.close',
@@ -417,6 +417,29 @@ class Peer {
417
417
  });
418
418
  }
419
419
 
420
+ reset() {
421
+ this.producers.clear();
422
+
423
+ this.tracks.forEach((x) => x.close());
424
+ this.tracks.clear();
425
+
426
+ const eventNames = [
427
+ MEDIASOUP_EVENTS.newProducer,
428
+ MEDIASOUP_EVENTS.producerClose,
429
+ MEDIASOUP_EVENTS.closeConsumer,
430
+ MEDIASOUP_EVENTS.pauseConsumer,
431
+ MEDIASOUP_EVENTS.resumeConsumer,
432
+ MEDIASOUP_EVENTS.consumerScoreChanged,
433
+ MEDIASOUP_EVENTS.consumerScoreChanged,
434
+ MEDIASOUP_EVENTS.producerScoreChanged,
435
+ MEDIASOUP_EVENTS.consumerChangePreferredLayers,
436
+ MEDIASOUP_EVENTS.producerSetMaxSpatialLayer,
437
+ CHANNEL_EVENTS.updatePeerAppData,
438
+ ];
439
+
440
+ eventNames.forEach((x) => this.observer.removeAllListeners(x));
441
+ }
442
+
420
443
  private emitConnectionQuality(): void {
421
444
  // after the initialization a new layer, or new producer, scores of producers and consumers drop for a short time
422
445
  setTimeout(() => {
@@ -72,6 +72,30 @@ class ChannelEventHandler {
72
72
  peer.observer.emit(CHANNEL_EVENTS.updatePeerAppData, appData);
73
73
  });
74
74
  }
75
+
76
+ private removeEventListeners() {
77
+ const { connection } = this.engine.network.socket;
78
+
79
+ if (!connection) {
80
+ return;
81
+ }
82
+
83
+ const eventNames = [
84
+ CHANNEL_EVENTS.channelEvent,
85
+ CHANNEL_EVENTS.channelJoin,
86
+ CHANNEL_EVENTS.channelLeave,
87
+ CHANNEL_EVENTS.activityConfirmationRequired,
88
+ CHANNEL_EVENTS.activityConfirmationExpired,
89
+ CHANNEL_EVENTS.activityConfirmationAcquired,
90
+ CHANNEL_EVENTS.updatePeerAppData,
91
+ ];
92
+
93
+ eventNames.forEach((x) => connection.removeAllListeners(x));
94
+ }
95
+
96
+ public reset() {
97
+ this.removeEventListeners();
98
+ }
75
99
  }
76
100
 
77
101
  export default ChannelEventHandler;
@@ -215,6 +215,37 @@ class MediaSoupEventHandler {
215
215
  });
216
216
  }
217
217
 
218
+ private removeEventListeners() {
219
+ const { connection } = this.engine.network.socket;
220
+
221
+ if (!connection) {
222
+ return;
223
+ }
224
+
225
+ const eventNames = [
226
+ MEDIASOUP_EVENTS.producerClose,
227
+ MEDIASOUP_EVENTS.newProducer,
228
+ MEDIASOUP_EVENTS.closeConsumer,
229
+ MEDIASOUP_EVENTS.resumeConsumer,
230
+ MEDIASOUP_EVENTS.pauseConsumer,
231
+ MEDIASOUP_EVENTS.consumerChangePreferredLayers,
232
+ MEDIASOUP_EVENTS.consumerScoreChanged,
233
+ MEDIASOUP_EVENTS.producerScoreChanged,
234
+ MEDIASOUP_EVENTS.producerRequestMaxSpatialLayer,
235
+ MEDIASOUP_EVENTS.producerSetMaxSpatialLayer,
236
+ MEDIASOUP_EVENTS.transportConnectionTimeout,
237
+ MEDIASOUP_EVENTS.producerPaused,
238
+ MEDIASOUP_EVENTS.producerResumed,
239
+ MEDIASOUP_EVENTS.producerForceClosed,
240
+ ];
241
+
242
+ eventNames.forEach((x) => connection.removeAllListeners(x));
243
+ }
244
+
245
+ public reset() {
246
+ this.removeEventListeners();
247
+ }
248
+
218
249
  private async handleProducerSetMaxSpatialLayer({
219
250
  producerId,
220
251
  spatialLayer,
@@ -24,8 +24,9 @@ import ChannelEventHandler from './handlers/ChannelEventHandler';
24
24
  import MediaSoupEventHandler from './handlers/MediaSoupEventHandler';
25
25
  import Logger from './Logger';
26
26
  import {
27
- CHANNEL_EVENTS, CLIENT_EVENTS, INTERNAL_CLIENT_EVENTS, SocketIOEvents,
27
+ CHANNEL_EVENTS, CLIENT_EVENTS, INTERNAL_CLIENT_EVENTS,
28
28
  } from '../constants/events';
29
+ import { SocketIOEvents } from '../types/socket';
29
30
  import {
30
31
  GetNodeRequest,
31
32
  JoinChannelRequest,
@@ -161,7 +162,6 @@ class Engine {
161
162
  });
162
163
 
163
164
  this.watchSocketState();
164
- this.watchClientEvents();
165
165
  }
166
166
 
167
167
  private async initialize(): Promise<void> {
@@ -182,10 +182,13 @@ class Engine {
182
182
  } catch (error: unknown) {
183
183
  this.logger.error('initialize()', { error });
184
184
 
185
- if (
186
- (error instanceof MediasoupUnsupportedError && error.message === 'device not supported')
187
- || (error instanceof Error && error.message === 'RTCPeerConnection is not defined')
188
- ) {
185
+ const isDeviceNotSupported = error instanceof MediasoupUnsupportedError
186
+ && error.message === 'device not supported';
187
+
188
+ const isNoRTCPeerConnection = error instanceof Error
189
+ && error.message === 'RTCPeerConnection is not defined';
190
+
191
+ if (isDeviceNotSupported || isNoRTCPeerConnection) {
189
192
  throw new UnsupportedError('Device not supported', UnsupportedError.UNSUPPORTED_DEVICE_CODE);
190
193
  }
191
194
 
@@ -200,13 +203,25 @@ class Engine {
200
203
  this.app = undefined;
201
204
  this.channel = undefined;
202
205
  this.webRtcIssueDetector?.stopWatchingNewPeerConnections();
206
+
207
+ this.network.reset();
203
208
  this.network.socket.disconnect();
209
+
210
+ this.peersRepository.forEach((x) => x.reset());
204
211
  this.peersRepository.clear();
212
+
205
213
  await this.network.closeSendTransport();
206
214
  await this.network.closeReceiveTransport();
207
215
  await this.media.clearTracks();
216
+
217
+ this.audioObserver?.removeListener('message', this.channelAudioObserverEventHandler.handle);
208
218
  this.audioObserver?.close();
209
219
 
220
+ this.channelEventsHandler.reset();
221
+ this.mediaSoupEventsHandler.reset();
222
+
223
+ this.removeClientEventEmitterListeners();
224
+
210
225
  this.logger.debug('release()', {
211
226
  socketId: this.mySocketId,
212
227
  });
@@ -216,8 +231,21 @@ class Engine {
216
231
  }
217
232
  }
218
233
 
234
+ private removeClientEventEmitterListeners(): void {
235
+ const eventNames = [
236
+ INTERNAL_CLIENT_EVENTS.trackProduced,
237
+ INTERNAL_CLIENT_EVENTS.trackUnproduced,
238
+ INTERNAL_CLIENT_EVENTS.trackPaused,
239
+ INTERNAL_CLIENT_EVENTS.trackResumed,
240
+ INTERNAL_CLIENT_EVENTS.trackClosed,
241
+ INTERNAL_CLIENT_EVENTS.trackReopened,
242
+ ];
243
+
244
+ eventNames.forEach((x) => this.clientEventEmitter.removeAllListeners(x));
245
+ }
246
+
219
247
  private watchSocketState(): void {
220
- this.network.socket.observer.on('state', (evt: SocketObserverEvent) => {
248
+ this.network.socket.observer.on(SocketIOEvents.State, (evt: SocketObserverEvent) => {
221
249
  const { state, code, error } = evt;
222
250
  this.clientEventEmitter.emit(state, { code, error });
223
251
 
@@ -274,6 +302,7 @@ class Engine {
274
302
  try {
275
303
  this.logger.debug('join()', { params });
276
304
  this.isChannelJoining = true;
305
+ this.watchClientEvents();
277
306
  await this.connectToSocketServerWithRetry(params);
278
307
  await this.performJoin({ appData: params.appData });
279
308
  } catch (error) {
@@ -316,6 +345,7 @@ class Engine {
316
345
  }
317
346
 
318
347
  peer.tracks.forEach((track) => track.close());
348
+ peer.reset();
319
349
  this.peersRepository.delete(peerId);
320
350
  }
321
351
 
@@ -407,7 +437,7 @@ class Engine {
407
437
  tracks.forEach((track) => {
408
438
  track.mediaStreamTrack.addEventListener('ended', () => {
409
439
  track.unpublish();
410
- });
440
+ }, { once: true });
411
441
 
412
442
  track.setStopTrackOnPause(Boolean(options?.stopTrackOnPause));
413
443
  if (track instanceof VideoTrack) {
@@ -521,7 +551,9 @@ class Engine {
521
551
  return new Promise((resolve, reject) => {
522
552
  const onSocketStateChange = async (data: SocketObserverEvent) => {
523
553
  const { error, state } = data;
524
- const stopListening = () => this.network.socket.observer.removeListener('state', onSocketStateChange);
554
+ const stopListening = () => this.network.socket.observer
555
+ .removeListener(SocketIOEvents.State, onSocketStateChange);
556
+
525
557
  const isStateNotExpected = [SocketIOEvents.Disconnected, SocketIOEvents.Reconnecting].includes(state);
526
558
 
527
559
  if (error) {
@@ -542,7 +574,7 @@ class Engine {
542
574
  }
543
575
  };
544
576
 
545
- this.network.socket.observer.on('state', onSocketStateChange);
577
+ this.network.socket.observer.on(SocketIOEvents.State, onSocketStateChange);
546
578
  });
547
579
  }
548
580
 
@@ -657,9 +689,7 @@ class Engine {
657
689
  channelId: this.channelId,
658
690
  });
659
691
 
660
- audioObserver.on('message', (data) => {
661
- this.channelAudioObserverEventHandler.handle(data);
662
- });
692
+ audioObserver.on('message', this.channelAudioObserverEventHandler.handle);
663
693
 
664
694
  this.audioObserver = audioObserver;
665
695
  this.logger.debug('Successfully create audio observer', logCtx);
@@ -1,6 +1,6 @@
1
1
  import { io, Socket } from 'socket.io-client';
2
2
  import EnhancedEventEmitter from '../../EnhancedEventEmitter';
3
- import { SocketIOEvents } from '../../constants/events';
3
+ import { SocketIOEvents } from '../../types/socket';
4
4
  import { LogLevel, LogMessageHandler, SocketResponse } from '../../types/common';
5
5
  import Logger from '../Logger';
6
6
 
@@ -63,7 +63,7 @@ class SocketIO {
63
63
  this.connection = connection;
64
64
  this.serverUrl = serverUrl;
65
65
 
66
- connection.on('connect', () => {
66
+ connection.on(SocketIOEvents.Connect, () => {
67
67
  const state = !this.reconnectingAttempt
68
68
  ? SocketIOEvents.Connected
69
69
  : SocketIOEvents.Reconnected;
@@ -71,39 +71,39 @@ class SocketIO {
71
71
  this.isConnected = true;
72
72
  this.setReconnectingAttempt(0);
73
73
 
74
- this.logger.debug('connection.on(`connect`)', {
74
+ this.logger.debug(`connection.on('${SocketIOEvents.Connect}')`, {
75
75
  connectionId: connection.id,
76
76
  });
77
77
 
78
- this.observer.safeEmit('state', { state });
78
+ this.observer.safeEmit(SocketIOEvents.State, { state });
79
79
  });
80
80
 
81
- connection.on('connect_error', (error: unknown) => {
81
+ connection.on(SocketIOEvents.ConnectError, (error: unknown) => {
82
82
  this.connectionError = error as string;
83
- this.logger.error('connection.on(`connect_error`)', { error });
84
- this.observer.safeEmit('state', { state: SocketIOEvents.Error, error });
83
+ this.logger.error(`connection.on('${SocketIOEvents.ConnectError}')`, { error });
84
+ this.observer.safeEmit(SocketIOEvents.State, { state: SocketIOEvents.Error, error });
85
85
  });
86
86
 
87
- connection.on('disconnect', ((reason: string) => {
87
+ connection.on(SocketIOEvents.Disconnect, ((reason: string) => {
88
88
  this.isConnected = false;
89
89
  this.setReconnectingAttempt(0);
90
90
  this.disconnectReason = reason;
91
- this.logger.warn('connection.on(`disconnect`)', { reason });
92
- this.observer.safeEmit('state', {
91
+ this.logger.warn(`connection.on('${SocketIOEvents.Disconnect}')`, { reason });
92
+ this.observer.safeEmit(SocketIOEvents.State, {
93
93
  state: SocketIOEvents.Disconnected,
94
94
  code: this.getDisconnectReasonCode(reason),
95
95
  });
96
96
  }));
97
97
 
98
- connection.io.on('reconnect_attempt', (attempt: number) => {
98
+ connection.io.on(SocketIOEvents.ReconnectAttempt, (attempt: number) => {
99
99
  this.setReconnectingAttempt(attempt);
100
- this.logger.warn('connection.on(`reconnect_attempt`)', { attempt });
101
- this.observer.safeEmit('state', { state: SocketIOEvents.Reconnecting });
100
+ this.logger.warn(`connection.on('${SocketIOEvents.ReconnectAttempt}')`, { attempt });
101
+ this.observer.safeEmit(SocketIOEvents.State, { state: SocketIOEvents.Reconnecting });
102
102
  });
103
103
 
104
- connection.io.on('reconnect', () => {
105
- this.logger.debug('connection.on(`reconnect`)', { connectionId: connection.id });
106
- this.observer.safeEmit('state', { state: SocketIOEvents.Reconnect });
104
+ connection.io.on(SocketIOEvents.Reconnect, () => {
105
+ this.logger.debug(`connection.on('${SocketIOEvents.Reconnect}')`, { connectionId: connection.id });
106
+ this.observer.safeEmit(SocketIOEvents.State, { state: SocketIOEvents.Reconnect });
107
107
  });
108
108
  }
109
109
 
@@ -112,6 +112,7 @@ class SocketIO {
112
112
  this.connection.offAny();
113
113
  this.connection.volatile.offAny();
114
114
  this.connection.disconnect();
115
+ this.connection.io.removeAllListeners();
115
116
  // we need to reset this counter in order not to get Reconnected event on retry
116
117
  this.setReconnectingAttempt(0);
117
118
  }
@@ -2,6 +2,7 @@ import {
2
2
  DtlsParameters,
3
3
  IceParameters,
4
4
  Transport,
5
+ TransportEvents,
5
6
  TransportOptions,
6
7
  } from 'mediasoup-client/lib/Transport';
7
8
  import { Device } from 'mediasoup-client';
@@ -64,6 +65,32 @@ class Network {
64
65
  });
65
66
  }
66
67
 
68
+ private removeEventListeners() {
69
+ const { sendTransport, receiveTransport } = this;
70
+
71
+ if (sendTransport) {
72
+ const eventNames: (keyof TransportEvents)[] = [
73
+ 'connect',
74
+ 'produce',
75
+ 'connectionstatechange',
76
+ ];
77
+ eventNames.forEach((x) => sendTransport.removeAllListeners(x));
78
+ }
79
+
80
+ if (receiveTransport) {
81
+ const eventNames: (keyof TransportEvents)[] = [
82
+ 'connect',
83
+ 'produce',
84
+ 'connectionstatechange',
85
+ ];
86
+ eventNames.forEach((x) => receiveTransport.removeAllListeners(x));
87
+ }
88
+ }
89
+
90
+ public reset() {
91
+ this.removeEventListeners();
92
+ }
93
+
67
94
  async closeRemoteProducer(producerId: string): Promise<void> {
68
95
  await this.socket.request(MEDIASOUP_EVENTS.producerClose, {
69
96
  peerId: this.socket.id,
@@ -1,5 +1,5 @@
1
- import { SocketIOEvents } from '../constants/events';
2
1
  import { Role } from './common';
2
+ import { SocketIOEvents } from './socket';
3
3
 
4
4
  export type GetNodeRequest = {
5
5
  channelId: string;
@@ -0,0 +1,16 @@
1
+ // eslint-disable-next-line import/prefer-default-export
2
+ export enum SocketIOEvents {
3
+ Connect = 'connect',
4
+ Connected = 'connected',
5
+ ConnectError = 'connect_error',
6
+
7
+ Reconnect = 'reconnect',
8
+ Reconnecting = 'reconnecting',
9
+ Reconnected = 'reconnected',
10
+ ReconnectAttempt = 'reconnect_attempt',
11
+
12
+ Disconnect = 'disconnect',
13
+ Disconnected = 'disconnected',
14
+ Error = 'error',
15
+ State = 'state',
16
+ }