@livedigital/client 2.42.0 → 2.44.0-audio-observer.1

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.
@@ -5,10 +5,11 @@ import {
5
5
  TransportOptions,
6
6
  } from 'mediasoup-client/lib/Transport';
7
7
  import { Device } from 'mediasoup-client';
8
- import { Consumer } from 'mediasoup-client/lib/Consumer';
8
+ import { DataConsumer, DataConsumerOptions } from 'mediasoup-client/lib/DataConsumer';
9
9
  import SocketIO from './Socket';
10
10
  import {
11
11
  CreateConsumerPayload,
12
+ CreateConsumerResponse,
12
13
  LogLevel,
13
14
  LogMessageHandler,
14
15
  ProduceParams,
@@ -17,6 +18,7 @@ import {
17
18
  TransportsStateInfo,
18
19
  TransportStateInfo,
19
20
  UpdatePeerAppDataPayload,
21
+ CreateDataConsumerPayload,
20
22
  } from '../../types/common';
21
23
  import LoadBalancerApiClient from './LoadBalancerClient';
22
24
  import { CHANNEL_EVENTS, MEDIASOUP_EVENTS, MEDIASOUP_TRANSPORT_EVENTS } from '../../constants/events';
@@ -108,25 +110,24 @@ class Network {
108
110
  localDirection: 'send',
109
111
  }) as TransportOptions;
110
112
 
111
- this.sendTransport = mediasoupDevice.createSendTransport(sendTransportOptions);
113
+ const transport = mediasoupDevice.createSendTransport(sendTransportOptions);
112
114
  this.logger.debug('createSendTransport()', { transport: this.sendTransport });
113
115
 
114
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
115
- this.sendTransport.on(MEDIASOUP_TRANSPORT_EVENTS.connect, async (
116
+ transport.on(MEDIASOUP_TRANSPORT_EVENTS.connect, async (
116
117
  { dtlsParameters }: { dtlsParameters: DtlsParameters },
117
118
  callback: () => void,
118
119
  errback: (error: Error) => void,
119
120
  ) => {
120
121
  try {
121
122
  await this.socket.request(MEDIASOUP_EVENTS.transportConnect, {
122
- transportId: this.sendTransport?.id,
123
+ transportId: transport.id,
123
124
  dtlsParameters,
124
125
  });
125
126
 
126
127
  callback();
127
128
 
128
129
  this.logger.debug('sendTransport.connect()', {
129
- transportId: this.sendTransport?.id,
130
+ transportId: transport.id,
130
131
  dtlsRole: dtlsParameters.role,
131
132
  });
132
133
  } catch (error) {
@@ -135,14 +136,14 @@ class Network {
135
136
  }
136
137
  });
137
138
 
138
- this.sendTransport.on(MEDIASOUP_TRANSPORT_EVENTS.produce, async (
139
+ transport.on(MEDIASOUP_TRANSPORT_EVENTS.produce, async (
139
140
  parameters: ProduceParams,
140
141
  callback: (a: unknown) => void,
141
142
  errback: (error: Error) => void,
142
143
  ) => {
143
144
  try {
144
145
  const { id } = await this.socket.request(MEDIASOUP_EVENTS.transportProduce, {
145
- transportId: this.sendTransport?.id,
146
+ transportId: transport.id,
146
147
  kind: parameters.kind,
147
148
  rtpParameters: parameters.rtpParameters,
148
149
  appData: parameters.appData,
@@ -152,68 +153,67 @@ class Network {
152
153
 
153
154
  this.logger.debug('sendTransport.produce()', {
154
155
  producerId: id,
155
- transportId: this.sendTransport?.id,
156
+ transportId: transport.id,
156
157
  kind: parameters.kind,
157
158
  });
158
159
  } catch (error) {
159
160
  this.logger.error('sendTransport.produce()', {
160
161
  error,
161
- transportId: this.sendTransport?.id,
162
+ transportId: transport.id,
162
163
  kind: parameters.kind,
163
164
  });
164
165
  errback(error);
165
166
  }
166
167
  });
167
168
 
168
- this.sendTransport.on(MEDIASOUP_EVENTS.transportStateChange, (state: RTCPeerConnectionState) => {
169
+ transport.on(MEDIASOUP_EVENTS.transportStateChange, (state: RTCPeerConnectionState) => {
169
170
  this.logger.debug('sendTransport.state', { state });
170
171
  if (state === 'failed') {
171
- if (this.sendTransport) {
172
- this.restartIce(this.sendTransport);
173
- this.logger.warn('sendTransport.restartIce()', {
174
- state,
175
- sendTransportId: this.sendTransport?.id,
176
- });
177
- }
172
+ this.restartIce(transport);
173
+ this.logger.warn('sendTransport.restartIce()', {
174
+ state,
175
+ sendTransportId: transport.id,
176
+ });
178
177
  }
179
178
 
180
179
  setTimeout(() => {
181
- if (this.sendTransport?.connectionState === 'disconnected' && this.socket.connection?.connected) {
180
+ if (transport.connectionState === 'disconnected' && this.socket.connection?.connected) {
182
181
  this.logger.warn('sendTransport is closed before websocket is closed', {
183
- sendTransportId: this.sendTransport.id,
182
+ sendTransportId: transport.id,
184
183
  });
185
184
  }
186
185
  }, 3000);
187
186
  });
187
+
188
+ this.sendTransport = transport;
188
189
  }
189
190
 
190
191
  async createRecvTransport(mediasoupDevice: Device): Promise<void> {
191
192
  if (this.receiveTransport) {
192
- this.logger.warn('createRecvTransport()', { message: 'Receive transport already exists' });
193
+ this.logger.warn('createReceiveTransport()', { message: 'Receive transport already exists' });
193
194
  return;
194
195
  }
195
196
 
196
197
  const recvTransportOptions = await this.socket.request(MEDIASOUP_EVENTS.transportCreate, {
197
198
  localDirection: 'receive',
198
199
  }) as TransportOptions;
199
- this.receiveTransport = mediasoupDevice.createRecvTransport(recvTransportOptions);
200
- this.logger.debug('createRecvTransport()', { transport: this.receiveTransport });
200
+ const transport = mediasoupDevice.createRecvTransport(recvTransportOptions);
201
+ this.logger.debug('createReceiveTransport()', { transport: this.receiveTransport });
201
202
 
202
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
203
- this.receiveTransport.on(MEDIASOUP_TRANSPORT_EVENTS.connect, async (
203
+ transport.on(MEDIASOUP_TRANSPORT_EVENTS.connect, async (
204
204
  { dtlsParameters }: { dtlsParameters: DtlsParameters },
205
205
  callback: () => void,
206
206
  errback: (error: Error) => void,
207
207
  ) => {
208
208
  try {
209
209
  await this.socket.request(MEDIASOUP_EVENTS.transportConnect, {
210
- transportId: this.receiveTransport?.id,
210
+ transportId: transport.id,
211
211
  dtlsParameters,
212
212
  });
213
213
  callback();
214
214
 
215
215
  this.logger.debug('receiveTransport.connect()', {
216
- transportId: this.receiveTransport?.id,
216
+ transportId: transport.id,
217
217
  dtlsRole: dtlsParameters.role,
218
218
  });
219
219
  } catch (error) {
@@ -222,23 +222,23 @@ class Network {
222
222
  }
223
223
  });
224
224
 
225
- this.receiveTransport.on(MEDIASOUP_EVENTS.transportStateChange, (state: RTCPeerConnectionState) => {
225
+ transport.on(MEDIASOUP_EVENTS.transportStateChange, (state: RTCPeerConnectionState) => {
226
226
  this.logger.debug('receiveTransport.state', { state });
227
227
  if (state === 'failed') {
228
- if (this.receiveTransport) {
229
- this.restartIce(this.receiveTransport);
230
- this.logger.debug('receiveTransport.restartIce()', { recvTransportId: this.receiveTransport.id });
231
- }
228
+ this.restartIce(transport);
229
+ this.logger.debug('receiveTransport.restartIce()', { recvTransportId: transport.id });
232
230
  }
233
231
 
234
232
  setTimeout(() => {
235
- if (this.receiveTransport?.connectionState === 'disconnected' && this.socket.connection?.connected) {
233
+ if (transport.connectionState === 'disconnected' && this.socket.connection?.connected) {
236
234
  this.logger.warn('receiveTransport is closed before websocket is closed', {
237
- recvTransportId: this.receiveTransport.id,
235
+ recvTransportId: transport.id,
238
236
  });
239
237
  }
240
238
  }, 3000);
241
239
  });
240
+
241
+ this.receiveTransport = transport;
242
242
  }
243
243
 
244
244
  async getIceParameters(transport: Transport): Promise<IceParameters> {
@@ -271,11 +271,11 @@ class Network {
271
271
  appId,
272
272
  channelId,
273
273
  producerPeerId,
274
- }: CreateConsumerPayload): Promise<{ consumer: Consumer, isProducerPaused: boolean }> {
274
+ }: CreateConsumerPayload): Promise<CreateConsumerResponse> {
275
275
  const transport = this.receiveTransport;
276
276
  if (!transport) {
277
- this.logger.error('createConsumer()', { reason: 'missing receiveTransport' });
278
- throw new Error('Client not initialized');
277
+ this.logger.error('createConsumer()', { reason: 'missing receive transport' });
278
+ throw new Error('Failed to create consumer');
279
279
  }
280
280
 
281
281
  this.logger.debug('createConsumer()', { producerId });
@@ -301,6 +301,34 @@ class Network {
301
301
  };
302
302
  }
303
303
 
304
+ async createDataConsumer({
305
+ producerId,
306
+ appId,
307
+ channelId,
308
+ }: CreateDataConsumerPayload): Promise<DataConsumer> {
309
+ const transport = this.receiveTransport;
310
+ if (!transport) {
311
+ this.logger.error('createDataConsumer()', { reason: 'missing receive transport' });
312
+ throw new Error('Failed to create consumer');
313
+ }
314
+
315
+ this.logger.debug('createDataConsumer()', { producerId });
316
+ const options = await this.socket.request(MEDIASOUP_EVENTS.createDataConsumer, {
317
+ dataProducerId: producerId,
318
+ transportId: transport.id,
319
+ appData: {
320
+ peerId: this.socket.id,
321
+ producerId,
322
+ appId,
323
+ channelId,
324
+ },
325
+ }) as DataConsumerOptions;
326
+
327
+ const consumer = await transport.consumeData(options);
328
+ this.logger.debug('Data consumer created', { consumer });
329
+ return consumer;
330
+ }
331
+
304
332
  async closeRemoteConsumer(consumerId: string): Promise<SocketResponse> {
305
333
  return this.socket.request(MEDIASOUP_EVENTS.closeConsumer, { peerId: this.socket.id, consumerId });
306
334
  }
@@ -317,6 +345,10 @@ class Network {
317
345
  return this.socket.request(CHANNEL_EVENTS.updatePeerAppData, { peerId, appData });
318
346
  }
319
347
 
348
+ async getAudioObserverProducer(): Promise<SocketResponse> {
349
+ return this.socket.request(CHANNEL_EVENTS.getAudioObserverProducer);
350
+ }
351
+
320
352
  async getTransportsStateInfo(): Promise<TransportsStateInfo> {
321
353
  const [receive, send] = await Promise.all([
322
354
  this.getParsedTransportStats(this.receiveTransport),
@@ -0,0 +1,9 @@
1
+ import LivedigitalSDKError from './LivedigitalSDKError';
2
+
3
+ class InvalidPayloadError extends LivedigitalSDKError {
4
+ constructor(msg?: string, errorCode?: string) {
5
+ super(msg, errorCode ?? 'invalid_payload');
6
+ }
7
+ }
8
+
9
+ export default InvalidPayloadError;
@@ -0,0 +1,9 @@
1
+ import LivedigitalSDKError from './LivedigitalSDKError';
2
+
3
+ class NeedJoinFirstError extends LivedigitalSDKError {
4
+ constructor(msg?: string, errorCode?: string) {
5
+ super(msg, errorCode ?? 'need_join_first');
6
+ }
7
+ }
8
+
9
+ export default NeedJoinFirstError;
@@ -0,0 +1,34 @@
1
+ import { TrackLabel } from './common';
2
+
3
+ export enum ChannelAudioObserverEvents {
4
+ DominantSpeaker = 'dominant-speaker',
5
+ PeersVolumes = 'peers-volumes',
6
+ Silence = 'silence',
7
+ }
8
+
9
+ export type DominantSpeakerEvent = {
10
+ event: ChannelAudioObserverEvents.DominantSpeaker;
11
+ data: {
12
+ peerId: string;
13
+ producerId: string;
14
+ trackLabel: TrackLabel.Microphone;
15
+ }
16
+ };
17
+
18
+ export type SilenceEvent = {
19
+ event: ChannelAudioObserverEvents.Silence;
20
+ };
21
+
22
+ export type PeerVolume = {
23
+ peerId: string;
24
+ producerId: string;
25
+ value: number;
26
+ trackLabel: TrackLabel.Microphone;
27
+ };
28
+
29
+ export type PeersVolumesEvent = {
30
+ event: ChannelAudioObserverEvents.PeersVolumes;
31
+ volumes: PeerVolume[]
32
+ };
33
+
34
+ export type AudioObserverEvents = DominantSpeakerEvent | SilenceEvent | PeersVolumesEvent;
@@ -3,7 +3,7 @@ import {
3
3
  RtpEncodingParameters,
4
4
  RtpParameters,
5
5
  } from 'mediasoup-client/lib/types';
6
- import { ConsumerOptions } from 'mediasoup-client/lib/Consumer';
6
+ import { Consumer, ConsumerOptions } from 'mediasoup-client/lib/Consumer';
7
7
  import { RtpCapabilities } from 'mediasoup-client/src/RtpParameters';
8
8
  import { ProducerCodecOptions } from 'mediasoup-client/lib/Producer';
9
9
  import { ConnectionState } from 'mediasoup-client/src/Transport';
@@ -287,6 +287,17 @@ export type CreateConsumerPayload = {
287
287
  producerPeerId: string,
288
288
  };
289
289
 
290
+ export type CreateDataConsumerPayload = {
291
+ producerId: string,
292
+ appId?: string,
293
+ channelId?: string,
294
+ };
295
+
296
+ export type CreateConsumerResponse = {
297
+ consumer: Consumer,
298
+ isProducerPaused: boolean,
299
+ };
300
+
290
301
  export enum DeviceErrors {
291
302
  NotFoundError = 'NotFoundError',
292
303
  NoDevices = 'NoDevices',
@@ -376,3 +387,8 @@ export interface TrackWithEncodings {
376
387
  getCodecOptions(): ProducerCodecOptions;
377
388
  getEncodings(): RtpEncodingParameters[];
378
389
  }
390
+
391
+ export type ActivityConfirmationRequiredPayload = {
392
+ channelId: string;
393
+ time: number;
394
+ };
@@ -8,6 +8,7 @@ import ChannelEventHandler from '../engine/handlers/ChannelEventHandler';
8
8
  import MediaSoupEventHandler from '../engine/handlers/MediaSoupEventHandler';
9
9
  import { LoadBalancerApiClientParams } from '../engine/network/LoadBalancerClient';
10
10
  import { Logger, LogLevel, LogMessageHandler } from './common';
11
+ import ChannelAudioObserverEventHandler from '../engine/handlers/ChannelAudioObserverEventHandler';
11
12
 
12
13
  export type IssuesHandler = (issues: IssueDetectorResult) => void;
13
14
 
@@ -54,11 +55,17 @@ export interface CreateChannelEventHandlerParams {
54
55
  onLogMessage?: LogMessageHandler;
55
56
  }
56
57
 
58
+ export interface CreateAudioObserverEventHandlerParams {
59
+ engine: Engine;
60
+ onLogMessage?: LogMessageHandler;
61
+ }
62
+
57
63
  export interface EngineDependenciesFactory {
58
64
  createSystem: (params: CreateSystemParams) => System;
59
65
  createMedia: (params: CreateMediaParams) => Media;
60
66
  createNetwork: (params: CreateNetworkParams) => Network;
61
67
  createChannelEventHandler: (params: CreateChannelEventHandlerParams) => ChannelEventHandler;
62
68
  createMediaSoupEventHandler: (params: CreateMediaSoupEventHandlerParams) => MediaSoupEventHandler;
69
+ createAudioObserverEventHandler: (params: CreateAudioObserverEventHandlerParams) => ChannelAudioObserverEventHandler;
63
70
  createIssueDetector: (params: CreateIssueDetectorParams) => WebRTCIssueDetector;
64
71
  }