@livedigital/client 3.0.7 → 3.0.9

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.9",
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,
@@ -103,6 +104,8 @@ class Engine {
103
104
 
104
105
  private rejoinRequested = false;
105
106
 
107
+ private channelAudioObserverEventHandle: (data: string) => void;
108
+
106
109
  constructor(params: EngineParams) {
107
110
  const {
108
111
  clientEventEmitter,
@@ -144,6 +147,10 @@ class Engine {
144
147
  onLogMessage: params.onLogMessage,
145
148
  });
146
149
 
150
+ this.channelAudioObserverEventHandle = (data) => {
151
+ this.channelAudioObserverEventHandler.handle(data);
152
+ };
153
+
147
154
  this.logger = new Logger({
148
155
  logLevel: this.logLevel,
149
156
  namespace: 'Engine',
@@ -161,7 +168,6 @@ class Engine {
161
168
  });
162
169
 
163
170
  this.watchSocketState();
164
- this.watchClientEvents();
165
171
  }
166
172
 
167
173
  private async initialize(): Promise<void> {
@@ -182,10 +188,13 @@ class Engine {
182
188
  } catch (error: unknown) {
183
189
  this.logger.error('initialize()', { error });
184
190
 
185
- if (
186
- (error instanceof MediasoupUnsupportedError && error.message === 'device not supported')
187
- || (error instanceof Error && error.message === 'RTCPeerConnection is not defined')
188
- ) {
191
+ const isDeviceNotSupported = error instanceof MediasoupUnsupportedError
192
+ && error.message === 'device not supported';
193
+
194
+ const isNoRTCPeerConnection = error instanceof Error
195
+ && error.message === 'RTCPeerConnection is not defined';
196
+
197
+ if (isDeviceNotSupported || isNoRTCPeerConnection) {
189
198
  throw new UnsupportedError('Device not supported', UnsupportedError.UNSUPPORTED_DEVICE_CODE);
190
199
  }
191
200
 
@@ -200,13 +209,25 @@ class Engine {
200
209
  this.app = undefined;
201
210
  this.channel = undefined;
202
211
  this.webRtcIssueDetector?.stopWatchingNewPeerConnections();
212
+
213
+ this.network.reset();
203
214
  this.network.socket.disconnect();
215
+
216
+ this.peersRepository.forEach((x) => x.reset());
204
217
  this.peersRepository.clear();
218
+
205
219
  await this.network.closeSendTransport();
206
220
  await this.network.closeReceiveTransport();
207
221
  await this.media.clearTracks();
222
+
223
+ this.audioObserver?.removeListener('message', this.channelAudioObserverEventHandle);
208
224
  this.audioObserver?.close();
209
225
 
226
+ this.channelEventsHandler.reset();
227
+ this.mediaSoupEventsHandler.reset();
228
+
229
+ this.removeClientEventEmitterListeners();
230
+
210
231
  this.logger.debug('release()', {
211
232
  socketId: this.mySocketId,
212
233
  });
@@ -216,8 +237,21 @@ class Engine {
216
237
  }
217
238
  }
218
239
 
240
+ private removeClientEventEmitterListeners(): void {
241
+ const eventNames = [
242
+ INTERNAL_CLIENT_EVENTS.trackProduced,
243
+ INTERNAL_CLIENT_EVENTS.trackUnproduced,
244
+ INTERNAL_CLIENT_EVENTS.trackPaused,
245
+ INTERNAL_CLIENT_EVENTS.trackResumed,
246
+ INTERNAL_CLIENT_EVENTS.trackClosed,
247
+ INTERNAL_CLIENT_EVENTS.trackReopened,
248
+ ];
249
+
250
+ eventNames.forEach((x) => this.clientEventEmitter.removeAllListeners(x));
251
+ }
252
+
219
253
  private watchSocketState(): void {
220
- this.network.socket.observer.on('state', (evt: SocketObserverEvent) => {
254
+ this.network.socket.observer.on(SocketIOEvents.State, (evt: SocketObserverEvent) => {
221
255
  const { state, code, error } = evt;
222
256
  this.clientEventEmitter.emit(state, { code, error });
223
257
 
@@ -274,6 +308,7 @@ class Engine {
274
308
  try {
275
309
  this.logger.debug('join()', { params });
276
310
  this.isChannelJoining = true;
311
+ this.watchClientEvents();
277
312
  await this.connectToSocketServerWithRetry(params);
278
313
  await this.performJoin({ appData: params.appData });
279
314
  } catch (error) {
@@ -316,6 +351,7 @@ class Engine {
316
351
  }
317
352
 
318
353
  peer.tracks.forEach((track) => track.close());
354
+ peer.reset();
319
355
  this.peersRepository.delete(peerId);
320
356
  }
321
357
 
@@ -407,7 +443,7 @@ class Engine {
407
443
  tracks.forEach((track) => {
408
444
  track.mediaStreamTrack.addEventListener('ended', () => {
409
445
  track.unpublish();
410
- });
446
+ }, { once: true });
411
447
 
412
448
  track.setStopTrackOnPause(Boolean(options?.stopTrackOnPause));
413
449
  if (track instanceof VideoTrack) {
@@ -521,7 +557,9 @@ class Engine {
521
557
  return new Promise((resolve, reject) => {
522
558
  const onSocketStateChange = async (data: SocketObserverEvent) => {
523
559
  const { error, state } = data;
524
- const stopListening = () => this.network.socket.observer.removeListener('state', onSocketStateChange);
560
+ const stopListening = () => this.network.socket.observer
561
+ .removeListener(SocketIOEvents.State, onSocketStateChange);
562
+
525
563
  const isStateNotExpected = [SocketIOEvents.Disconnected, SocketIOEvents.Reconnecting].includes(state);
526
564
 
527
565
  if (error) {
@@ -542,7 +580,7 @@ class Engine {
542
580
  }
543
581
  };
544
582
 
545
- this.network.socket.observer.on('state', onSocketStateChange);
583
+ this.network.socket.observer.on(SocketIOEvents.State, onSocketStateChange);
546
584
  });
547
585
  }
548
586
 
@@ -657,9 +695,7 @@ class Engine {
657
695
  channelId: this.channelId,
658
696
  });
659
697
 
660
- audioObserver.on('message', (data) => {
661
- this.channelAudioObserverEventHandler.handle(data);
662
- });
698
+ audioObserver.on('message', this.channelAudioObserverEventHandle);
663
699
 
664
700
  this.audioObserver = audioObserver;
665
701
  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
+ }