@livedigital/client 3.25.2 → 3.25.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/engine/{ChannelStateSynchronizer.d.ts → ChannelStateSynchronyzer/ChannelStateSynchronizer.d.ts} +4 -6
  2. package/dist/engine/ChannelStateSynchronyzer/types.d.ts +17 -0
  3. package/dist/engine/DefaultEngineDependenciesFactory.d.ts +1 -1
  4. package/dist/engine/handlers/ChannelStateSyncEventHandler/ChannelStateConsistencyCheckResult.d.ts +1 -9
  5. package/dist/engine/handlers/ChannelStateSyncEventHandler/ChannelStateConsistencyChecker.d.ts +0 -7
  6. package/dist/engine/handlers/ChannelStateSyncEventHandler/index.d.ts +2 -2
  7. package/dist/engine/handlers/ChannelStateSyncEventHandler/types.d.ts +10 -3
  8. package/dist/errors/LivedigitalSDKError.d.ts +2 -1
  9. package/dist/errors/RequestError.d.ts +5 -0
  10. package/dist/index.es.js +3 -3
  11. package/dist/index.js +4 -4
  12. package/dist/types/engine.d.ts +3 -1
  13. package/package.json +1 -1
  14. package/src/engine/{ChannelStateSynchronizer.ts → ChannelStateSynchronyzer/ChannelStateSynchronizer.ts} +118 -70
  15. package/src/engine/ChannelStateSynchronyzer/types.ts +22 -0
  16. package/src/engine/DefaultEngineDependenciesFactory.ts +3 -1
  17. package/src/engine/handlers/ChannelStateSyncEventHandler/ChannelStateConsistencyCheckResult.ts +1 -12
  18. package/src/engine/handlers/ChannelStateSyncEventHandler/ChannelStateConsistencyChecker.ts +3 -40
  19. package/src/engine/handlers/ChannelStateSyncEventHandler/index.ts +60 -45
  20. package/src/engine/handlers/ChannelStateSyncEventHandler/types.ts +9 -4
  21. package/src/engine/index.ts +2 -1
  22. package/src/engine/media/tracks/PeerTrack.ts +2 -11
  23. package/src/engine/network/Socket.ts +2 -1
  24. package/src/engine/network/index.ts +34 -11
  25. package/src/errors/LivedigitalSDKError.ts +4 -1
  26. package/src/errors/RequestError.ts +9 -0
  27. package/src/types/engine.ts +5 -1
@@ -20,22 +20,16 @@ import {
20
20
  MAX_CHECK_RETRIES_BEFORE_CONFIRM,
21
21
  SIGNALING_PING_TIMEOUT,
22
22
  } from './consts';
23
+ import ChannelStateSynchronizer from '../../ChannelStateSynchronyzer/ChannelStateSynchronizer';
24
+ import { RestoreChannelStateResult } from '../../ChannelStateSynchronyzer/types';
25
+
26
+ const RESTORE_STATE_RECOMMENDED_INTERVAL = 15_000;
23
27
 
24
28
  class ChannelStateSyncEventHandler {
25
29
  private readonly engine: Engine;
26
30
 
27
31
  private readonly logger: Logger;
28
32
 
29
- #confirmedMissingPeers: string[] = [];
30
-
31
- #confirmedMissingConsumers: string[] = [];
32
-
33
- #confirmedMissingProducers: string[] = [];
34
-
35
- #confirmedConsumersWithInconsistentState: string[] = [];
36
-
37
- #confirmedProducersWithInconsistentState: string[] = [];
38
-
39
33
  #checkResults: ChannelStateConsistencyCheckResult[] = [];
40
34
 
41
35
  #channelStateEventHandling: boolean = false;
@@ -49,13 +43,18 @@ class ChannelStateSyncEventHandler {
49
43
  outbound: EXCELLENT_NETWORK_SCORE,
50
44
  };
51
45
 
46
+ readonly #channelStateSynchronizer: ChannelStateSynchronizer;
47
+
48
+ #lastSyncTime: number = new Date().getTime() - RESTORE_STATE_RECOMMENDED_INTERVAL;
49
+
52
50
  constructor(params: ChannelStateSyncEventHandlerParams) {
53
51
  const {
54
- engine, onLogMessage, clientEventEmitter, webRTCIssueEmitter,
52
+ engine, onLogMessage, clientEventEmitter, webRTCIssueEmitter, channelStateSynchronizer,
55
53
  } = params;
56
54
  this.engine = engine;
57
55
  this.clientEventEmitter = clientEventEmitter;
58
56
  this.webRTCIssueEmitter = webRTCIssueEmitter;
57
+ this.#channelStateSynchronizer = channelStateSynchronizer;
59
58
  this.logger = new Logger({
60
59
  logLevel: engine.logLevel,
61
60
  namespace: 'ChannelStateSyncEventHandler',
@@ -70,7 +69,7 @@ class ChannelStateSyncEventHandler {
70
69
  try {
71
70
  const { event, data } = await this.parseChannelStateSyncEventPayload(payload);
72
71
  if (event === 'channel-state' && !this.#channelStateEventHandling) {
73
- this.handleChannelStateEvent(data);
72
+ await this.handleChannelStateEvent(data);
74
73
  }
75
74
  } catch (error: unknown) {
76
75
  this.logger.error('handleChannelStateSyncEvent()', {
@@ -84,9 +83,27 @@ class ChannelStateSyncEventHandler {
84
83
  try {
85
84
  await this.collectProbableInconsistencyCases(remoteState);
86
85
  const confirmedInconsistencyResult = this.getConfirmedInconsistencyResult();
87
- if (confirmedInconsistencyResult && confirmedInconsistencyResult.hasInconsistencyCases()) {
88
- this.handleConfirmedInconsistencyResult(confirmedInconsistencyResult);
86
+
87
+ if (!confirmedInconsistencyResult?.hasInconsistencyCases()) {
88
+ return;
89
89
  }
90
+
91
+ if (this.isRestoreStateRequestToEarly()) {
92
+ this.logger.info('Inconsistency detected, but restore skipped', { ...confirmedInconsistencyResult });
93
+ return;
94
+ }
95
+
96
+ const restoreResult = await this.#channelStateSynchronizer.restoreState();
97
+ const updatedCheckResult = this.compareCheckedAndRestoredResults(confirmedInconsistencyResult, restoreResult);
98
+
99
+ if (!updatedCheckResult.hasInconsistencyCases()) {
100
+ this.#checkResults[this.#checkResults.length - 1] = updatedCheckResult;
101
+ this.logger.info('Inconsistency has been corrected', { ...restoreResult });
102
+ return;
103
+ }
104
+
105
+ this.logger.warn('Inconsistency was detected', { ...updatedCheckResult });
106
+ this.handleConfirmedInconsistencyResult(updatedCheckResult);
90
107
  } finally {
91
108
  this.#channelStateEventHandling = false;
92
109
  }
@@ -98,7 +115,6 @@ class ChannelStateSyncEventHandler {
98
115
 
99
116
  private handleConfirmedInconsistencyResult(confirmedInconsistencyResult: ChannelStateConsistencyCheckResult): void {
100
117
  confirmedInconsistencyResult.missingPeers.forEach((peerId) => {
101
- this.#confirmedMissingPeers.push(peerId);
102
118
  this.clientEventEmitter.safeEmit(CLIENT_EVENTS.channelStateInconsistent, {
103
119
  peerId,
104
120
  type: InconsistenceType.MissingPeer,
@@ -106,7 +122,6 @@ class ChannelStateSyncEventHandler {
106
122
  });
107
123
 
108
124
  confirmedInconsistencyResult.missingProducers.forEach((producerId) => {
109
- this.#confirmedMissingProducers.push(producerId);
110
125
  this.clientEventEmitter.safeEmit(CLIENT_EVENTS.channelStateInconsistent, {
111
126
  producerId,
112
127
  type: InconsistenceType.MissingProducer,
@@ -114,27 +129,25 @@ class ChannelStateSyncEventHandler {
114
129
  });
115
130
 
116
131
  confirmedInconsistencyResult.producersWithInconsistentState.forEach((producerId) => {
117
- this.#confirmedProducersWithInconsistentState.push(producerId);
118
132
  this.clientEventEmitter.safeEmit(CLIENT_EVENTS.channelStateInconsistent, {
119
133
  producerId,
120
134
  type: InconsistenceType.IncorrectProducerState,
121
135
  });
122
136
  });
123
137
 
124
- if (confirmedInconsistencyResult.missingConsumers.length > 0) {
125
- this.#confirmedMissingConsumers.push(...confirmedInconsistencyResult.missingConsumers);
138
+ confirmedInconsistencyResult.peersWithInconsistentAppData.forEach((peerId) => {
126
139
  this.clientEventEmitter.safeEmit(CLIENT_EVENTS.channelStateInconsistent, {
127
- type: InconsistenceType.MissingConsumers,
140
+ peerId,
141
+ type: InconsistenceType.IncorrectAppData,
128
142
  });
129
- }
143
+ });
130
144
 
131
- if (confirmedInconsistencyResult.consumersWithInconsistentState.length > 0) {
132
- this.#confirmedConsumersWithInconsistentState
133
- .push(...confirmedInconsistencyResult.consumersWithInconsistentState);
145
+ confirmedInconsistencyResult.consumersWithInconsistentState.forEach((consumerId) => {
134
146
  this.clientEventEmitter.safeEmit(CLIENT_EVENTS.channelStateInconsistent, {
147
+ consumerId,
135
148
  type: InconsistenceType.IncorrectConsumerState,
136
149
  });
137
- }
150
+ });
138
151
  }
139
152
 
140
153
  private getConfirmedInconsistencyResult(): ChannelStateConsistencyCheckResult | undefined {
@@ -145,7 +158,6 @@ class ChannelStateSyncEventHandler {
145
158
 
146
159
  const confirmedCheckResult = new ChannelStateConsistencyCheckResult({
147
160
  missingPeers: this.getConfirmedMissingPeers(lastCheckResult.missingPeers),
148
- missingConsumers: this.getConfirmedMissingConsumers(lastCheckResult.missingConsumers),
149
161
  missingProducers: this.getConfirmedMissingProducers(lastCheckResult.missingProducers),
150
162
  consumersWithInconsistentState: this.getConfirmedConsumersWithInconsistencState(
151
163
  lastCheckResult.consumersWithInconsistentState,
@@ -163,7 +175,6 @@ class ChannelStateSyncEventHandler {
163
175
  const checker = new ChannelStateConsistencyChecker({
164
176
  remoteState,
165
177
  localState: this.getLocalState(),
166
- ignoreIds: this.getAllConfirmedInconsistentEntityIds(),
167
178
  });
168
179
 
169
180
  const checkResult = checker.check();
@@ -215,14 +226,6 @@ class ChannelStateSyncEventHandler {
215
226
  return peerIds.filter((peerId) => this.isReachedMaxCheckRetries('missingPeers', peerId));
216
227
  }
217
228
 
218
- private getConfirmedMissingConsumers(consumersIds: string[]): string[] {
219
- return consumersIds.filter((consumerId) => this.isReachedMaxCheckRetries(
220
- 'missingConsumers',
221
- consumerId,
222
- 1,
223
- ));
224
- }
225
-
226
229
  private getConfirmedMissingProducers(producerIds: string[]): string[] {
227
230
  return producerIds.filter((producerId) => this.isReachedMaxCheckRetries('missingProducers', producerId));
228
231
  }
@@ -254,16 +257,6 @@ class ChannelStateSyncEventHandler {
254
257
  return JSON.parse(strFromU8(decompressed)) as ChannelStateSyncEventPayload;
255
258
  }
256
259
 
257
- private getAllConfirmedInconsistentEntityIds(): string[] {
258
- return [
259
- ...this.#confirmedMissingPeers,
260
- ...this.#confirmedMissingConsumers,
261
- ...this.#confirmedMissingProducers,
262
- ...this.#confirmedConsumersWithInconsistentState,
263
- ...this.#confirmedProducersWithInconsistentState,
264
- ];
265
- }
266
-
267
260
  private getLocalState(): ChannelState {
268
261
  const { hostPeers } = this.engine;
269
262
  return {
@@ -311,6 +304,28 @@ class ChannelStateSyncEventHandler {
311
304
  const { inbound, outbound } = this.networkScores;
312
305
  return inbound <= BAD_NETWORK_SCORE || outbound <= BAD_NETWORK_SCORE;
313
306
  }
307
+
308
+ private compareCheckedAndRestoredResults(
309
+ checkResult: ChannelStateConsistencyCheckResult,
310
+ restoreResult: RestoreChannelStateResult,
311
+ ): ChannelStateConsistencyCheckResult {
312
+ return new ChannelStateConsistencyCheckResult({
313
+ missingPeers: checkResult.missingPeers
314
+ .filter((id) => !restoreResult.missingPeers.includes(id)),
315
+ missingProducers: checkResult.missingProducers
316
+ .filter((id) => !restoreResult.missingProducers.includes(id)),
317
+ consumersWithInconsistentState: checkResult.consumersWithInconsistentState
318
+ .filter((id) => !restoreResult.fixedConsumersState.includes(id)),
319
+ producersWithInconsistentState: checkResult.producersWithInconsistentState
320
+ .filter((id) => !restoreResult.fixedProducersState.includes(id)),
321
+ peersWithInconsistentAppData: checkResult.peersWithInconsistentAppData
322
+ .filter((id) => !restoreResult.restoredAppDataPeers.includes(id)),
323
+ });
324
+ }
325
+
326
+ private isRestoreStateRequestToEarly(): boolean {
327
+ return (new Date().getTime() - this.#lastSyncTime) <= RESTORE_STATE_RECOMMENDED_INTERVAL;
328
+ }
314
329
  }
315
330
 
316
331
  export default ChannelStateSyncEventHandler;
@@ -1,16 +1,21 @@
1
- /* eslint-disable import/prefer-default-export */
2
-
3
1
  export enum InconsistenceType {
4
2
  MissingPeer = 'missing-peer',
5
- MissingConsumers = 'missing-consumers',
6
3
  MissingProducer = 'missing-producer',
7
4
  IncorrectProducerState = 'incorrect-producer-state',
8
5
  IncorrectConsumerState = 'incorrect-consumer-state',
6
+ IncorrectAppData = 'incorrect-app-data',
9
7
  }
10
8
 
11
9
  export type StorageKey = 'missingPeers'
12
- | 'missingConsumers'
13
10
  | 'missingProducers'
14
11
  | 'consumersWithInconsistentState'
15
12
  | 'producersWithInconsistentState'
16
13
  | 'peersWithInconsistentAppData';
14
+
15
+ export interface ChannelStateConsistencyCheckResultsParams {
16
+ missingPeers: string[];
17
+ missingProducers: string[];
18
+ consumersWithInconsistentState: string[];
19
+ producersWithInconsistentState: string[];
20
+ peersWithInconsistentAppData: string[];
21
+ }
@@ -56,7 +56,7 @@ import {
56
56
  } from '../types/media';
57
57
  import ChannelStateSyncEventHandler from './handlers/ChannelStateSyncEventHandler';
58
58
  import { getSupportedCodecsMimeTypes } from '../helpers/media';
59
- import ChannelStateSynchronizer from './ChannelStateSynchronizer';
59
+ import ChannelStateSynchronizer from './ChannelStateSynchronyzer/ChannelStateSynchronizer';
60
60
  import EventsQueue from './EventsQueue';
61
61
  import clientMetaProvider from '../ClientMetaProvider';
62
62
 
@@ -222,6 +222,7 @@ class Engine {
222
222
  webRTCIssueEmitter: this.webRtcIssueDetector?.eventEmitter,
223
223
  onLogMessage: params.onLogMessage,
224
224
  sendAnalytics: this.#sendAnalytics,
225
+ channelStateSynchronizer: this.channelStateSynchronizer,
225
226
  });
226
227
 
227
228
  this.handleChannelAudioObserverEvent = (data) => {
@@ -169,11 +169,6 @@ class PeerTrack {
169
169
  return;
170
170
  }
171
171
 
172
- if (this.#paused) {
173
- this.#logger.debug('pause()', { message: 'Already paused', track: this.logCtx, consumer: this.consumer });
174
- return;
175
- }
176
-
177
172
  await this.pauseConsumer(pauseRemote);
178
173
  this.#paused = true;
179
174
  this.#peerEventEmitter.safeEmit(PEER_EVENTS.trackPaused, this);
@@ -189,11 +184,6 @@ class PeerTrack {
189
184
  return;
190
185
  }
191
186
 
192
- if (!this.#paused) {
193
- this.#logger.debug('resume()', { message: 'Already playing', track: this.logCtx });
194
- return;
195
- }
196
-
197
187
  await this.resumeConsumer(resumeRemote);
198
188
  this.#paused = false;
199
189
  this.#peerEventEmitter.safeEmit(PEER_EVENTS.trackResumed, this);
@@ -214,11 +204,12 @@ class PeerTrack {
214
204
  }
215
205
 
216
206
  try {
207
+ this.consumer.resume();
208
+
217
209
  if (resumeRemote) {
218
210
  await this.#engine.network.resumeRemoteConsumer(this.consumer.id);
219
211
  }
220
212
 
221
- this.consumer.resume();
222
213
  this.checkConsumerState();
223
214
  this.#logger.debug('resumeConsumer()', { track: this.logCtx });
224
215
  } catch (error) {
@@ -5,6 +5,7 @@ import { LogLevel, LogMessageHandler, SocketResponse } from '../../types/common'
5
5
  import Logger from '../Logger';
6
6
  import { SIGNALING_API_VERSION } from '../../constants/common';
7
7
  import { MEDIASOUP_MEDIA_ACTIONS_TRACK_EVENTS } from '../../constants/events';
8
+ import RequestError from '../../errors/RequestError';
8
9
 
9
10
  export type SocketIOConstructorParams = {
10
11
  logLevel: LogLevel,
@@ -177,7 +178,7 @@ class SocketIO {
177
178
  });
178
179
  }
179
180
 
180
- reject(response.error);
181
+ reject(new RequestError(response.error, response.errorCode, { ...response }));
181
182
  } else {
182
183
  if (this.#isBuffering) {
183
184
  this.#isBuffering = false;
@@ -35,6 +35,7 @@ import Logger from '../Logger';
35
35
  import EnhancedEventEmitter from '../../EnhancedEventEmitter';
36
36
  import { SocketIOEvents } from '../../types/socket';
37
37
  import { SocketObserverEvent, NetworkObserverEvents } from '../../types/network';
38
+ import RequestError from '../../errors/RequestError';
38
39
 
39
40
  export type NetworkParams = {
40
41
  socketClient: SocketIO;
@@ -252,20 +253,42 @@ class Network {
252
253
  }
253
254
 
254
255
  this.logger.debug('createConsumer()', { producerId });
255
- const consumerOptions = await this.socket.request(MEDIASOUP_EVENTS.createConsumer, {
256
- producerId,
257
- transportId: transport.id,
258
- rtpCapabilities,
259
- appData: {
260
- peerId: this.peerId,
261
- appId,
262
- channelId,
263
- },
264
- producerPeerId,
265
- }) as RemoteConsumerOptions;
256
+ let consumerOptions: RemoteConsumerOptions;
257
+ try {
258
+ consumerOptions = await this.socket.request<RemoteConsumerOptions>(MEDIASOUP_EVENTS.createConsumer, {
259
+ producerId,
260
+ transportId: transport.id,
261
+ rtpCapabilities,
262
+ appData: {
263
+ peerId: this.peerId,
264
+ appId,
265
+ channelId,
266
+ },
267
+ producerPeerId,
268
+ });
269
+ } catch (error) {
270
+ if (error instanceof RequestError && error.meta?.existedConsumer) {
271
+ consumerOptions = error.meta.existedConsumer as RemoteConsumerOptions;
272
+ } else {
273
+ this.logger.error('Failed to get consumer options', {
274
+ producerId,
275
+ rtpCapabilities,
276
+ appId,
277
+ channelId,
278
+ producerPeerId,
279
+ error: serializeError(error),
280
+ });
281
+ throw new Error('Failed to create consumer');
282
+ }
283
+ }
266
284
 
267
285
  const consumer = await transport.consume(consumerOptions);
268
286
  const { producerPaused: isProducerPaused } = consumerOptions;
287
+
288
+ if (isProducerPaused) {
289
+ consumer.pause();
290
+ }
291
+
269
292
  this.logger.debug('Consumer created', { consumerId: consumer.id, label: consumer.appData.label, isProducerPaused });
270
293
 
271
294
  return {
@@ -1,9 +1,12 @@
1
1
  class LivedigitalSDKError extends Error {
2
2
  readonly errorCode: string;
3
3
 
4
- constructor(msg?: string, errorCode?: string) {
4
+ readonly meta?: Record<string, unknown>;
5
+
6
+ constructor(msg?: string, errorCode?: string, meta?: Record<string, unknown>) {
5
7
  super(msg);
6
8
  this.errorCode = errorCode ?? 'unknown_error';
9
+ this.meta = meta;
7
10
  }
8
11
  }
9
12
 
@@ -0,0 +1,9 @@
1
+ import LivedigitalSDKError from './LivedigitalSDKError';
2
+
3
+ class RequestError extends LivedigitalSDKError {
4
+ constructor(msg?: string, errorCode?: string, meta?: Record<string, unknown>) {
5
+ super(msg, errorCode ?? 'something_went_wrong', meta);
6
+ }
7
+ }
8
+
9
+ export default RequestError;
@@ -26,7 +26,9 @@ import {
26
26
  InitEffectsSDKParams,
27
27
  } from './media';
28
28
  import ChannelStateSyncEventHandler from '../engine/handlers/ChannelStateSyncEventHandler';
29
- import ChannelStateSynchronizer, { ChannelStateSynchronizerParams } from '../engine/ChannelStateSynchronizer';
29
+ import ChannelStateSynchronizer, {
30
+ ChannelStateSynchronizerParams,
31
+ } from '../engine/ChannelStateSynchronyzer/ChannelStateSynchronizer';
30
32
  import { CLIENT_EVENTS, INTERNAL_CLIENT_EVENTS } from '../constants/events';
31
33
  import Peer from '../engine/Peer';
32
34
  import { InconsistenceType } from '../engine/handlers/ChannelStateSyncEventHandler/types';
@@ -94,6 +96,7 @@ export interface ChannelStateSyncEventHandlerParams {
94
96
  webRTCIssueEmitter?: WebRTCIssueEmitter;
95
97
  onLogMessage?: LogMessageHandler;
96
98
  sendAnalytics: boolean;
99
+ channelStateSynchronizer: ChannelStateSynchronizer;
97
100
  }
98
101
 
99
102
  export interface ConnectParams {
@@ -112,6 +115,7 @@ export interface ChannelStateInconsistentPayload {
112
115
  type: InconsistenceType;
113
116
  peerId?: string;
114
117
  producerId?: string;
118
+ consumerId?: string;
115
119
  }
116
120
 
117
121
  export interface ProducePermissionsChangedPayload {