@livedigital/client 1.1.2 → 1.2.0

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.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@livedigital/client",
3
3
  "author": "vlprojects",
4
4
  "license": "MIT",
5
- "version": "1.1.2",
5
+ "version": "1.2.0",
6
6
  "private": false,
7
7
  "bugs": {
8
8
  "url": "https://github.com/vlprojects/livedigital-sdk/issues"
@@ -47,7 +47,7 @@ class Peer {
47
47
 
48
48
  public uid?: string;
49
49
 
50
- public appData = {};
50
+ public appData: Record<string, unknown>;
51
51
 
52
52
  private videoProducer?: ProducerData;
53
53
 
@@ -75,7 +75,8 @@ class Peer {
75
75
  videoConsumer,
76
76
  audioConsumer,
77
77
  engine,
78
- appData = {},
78
+ appData,
79
+ uid,
79
80
  }: PeerConstructor) {
80
81
  this.id = id;
81
82
  this.channelId = channelId;
@@ -84,7 +85,8 @@ class Peer {
84
85
  this.isVirtual = isVirtual;
85
86
  this.originChannelId = originChannelId;
86
87
  this.originPeerId = originPeerId;
87
- this.appData = appData;
88
+ this.appData = appData || {};
89
+ this.uid = uid;
88
90
  this.engine = engine;
89
91
  this.videoConsumer = videoConsumer;
90
92
  this.audioConsumer = audioConsumer;
@@ -163,7 +165,7 @@ class Peer {
163
165
  }
164
166
 
165
167
  if (this.videoConsumer) {
166
- throw new Error('Already subscribed');
168
+ return;
167
169
  }
168
170
 
169
171
  await this.createConsumer(this.videoProducer);
@@ -175,7 +177,7 @@ class Peer {
175
177
  }
176
178
 
177
179
  if (this.audioConsumer) {
178
- throw new Error('Already subscribed');
180
+ return;
179
181
  }
180
182
 
181
183
  await this.createConsumer(this.audioProducer);
@@ -357,7 +359,7 @@ class Peer {
357
359
  consumer.close();
358
360
  } catch (err) {
359
361
  this.logger.error('closeConsumer()', consumer, err);
360
- throw new Error('Error subscribe media');
362
+ throw new Error('Error unsubscribe media');
361
363
  }
362
364
  }
363
365
 
@@ -384,7 +386,7 @@ class Peer {
384
386
  });
385
387
  } catch (err) {
386
388
  this.logger.error('changeConsumerPreferredLayers()', { consumerId: this.videoConsumer.id });
387
- throw new Error('Error subscribe media');
389
+ throw new Error('Error change preferred layer');
388
390
  }
389
391
  }
390
392
 
@@ -440,10 +442,10 @@ class Peer {
440
442
  if (producer.kind === ProducerKind.VIDEO) {
441
443
  this.setVideoProducer(producer);
442
444
  } else {
443
- this.setVideoProducer(producer);
445
+ this.setAudioProducer(producer);
444
446
  }
445
447
 
446
- this.engine.clientEventEmitter.safeEmit(PEER_EVENTS.mediaPublished, { kind: producer.kind });
448
+ this.observer.safeEmit(PEER_EVENTS.mediaPublished, { kind: producer.kind });
447
449
  });
448
450
 
449
451
  this.observer.on(MEDIASOUP_EVENTS.producerClose, (producer: ProducerData) => {
@@ -453,7 +455,22 @@ class Peer {
453
455
  this.removeAudioProducer();
454
456
  }
455
457
 
456
- this.engine.clientEventEmitter.safeEmit(PEER_EVENTS.mediaUnPublished, { kind: producer.kind });
458
+ this.observer.safeEmit(PEER_EVENTS.mediaUnPublished, { kind: producer.kind });
459
+ });
460
+
461
+ this.observer.on(MEDIASOUP_EVENTS.closeConsumer, (consumerId) => {
462
+ if (this.videoConsumer?.id === consumerId) {
463
+ this.videoConsumer?.close();
464
+ this.videoConsumer = undefined;
465
+ this.observer.safeEmit(PEER_EVENTS.trackEnd, { kind: 'video' });
466
+ return;
467
+ }
468
+
469
+ if (this.audioConsumer?.id === consumerId) {
470
+ this.audioConsumer?.close();
471
+ this.audioConsumer = undefined;
472
+ this.observer.safeEmit(PEER_EVENTS.trackEnd, { kind: 'audio' });
473
+ }
457
474
  });
458
475
 
459
476
  this.observer.on(MEDIASOUP_EVENTS.pauseConsumer, (consumerId) => {
@@ -33,6 +33,15 @@ class MediaSoupEventHandler {
33
33
  peer.observer.safeEmit(MEDIASOUP_EVENTS.newProducer, producer);
34
34
  });
35
35
 
36
+ connection.on(MEDIASOUP_EVENTS.closeConsumer, (consumerId: string, peerId: string) => {
37
+ const peer = this.engine.peers.find((item) => item.id === peerId);
38
+ if (!peer) {
39
+ return;
40
+ }
41
+
42
+ peer.observer.safeEmit(MEDIASOUP_EVENTS.closeConsumer, consumerId);
43
+ });
44
+
36
45
  connection.on(MEDIASOUP_EVENTS.resumeConsumer, (consumerId: string, peerId: string) => {
37
46
  const peer = this.engine.peers.find((item) => item.id === peerId);
38
47
  if (!peer || peer.isMe) {
@@ -50,7 +50,8 @@ class Engine {
50
50
 
51
51
  private async initialize(): Promise<void> {
52
52
  try {
53
- const rtpCapabilities = await this.network.socket.request('router.getRtpCapabilities') as RtpCapabilities;
53
+ const { rtpCapabilities } = await this.network.socket
54
+ .request('router.getRtpCapabilities') as { rtpCapabilities: RtpCapabilities };
54
55
  await this.media.loadDevice(rtpCapabilities);
55
56
  await this.network.createSendTransport(this.media.mediasoupDevice);
56
57
  await this.network.createRecvTransport(this.media.mediasoupDevice);
@@ -58,6 +59,7 @@ class Engine {
58
59
  this.logger.debug('initialize()');
59
60
  } catch (error) {
60
61
  this.logger.error('initialize()', error);
62
+ throw new Error('Error initialize engine');
61
63
  }
62
64
  }
63
65
 
@@ -85,6 +87,7 @@ class Engine {
85
87
  this.logger.debug('release()');
86
88
  } catch (err) {
87
89
  this.logger.error('release()', err);
90
+ throw new Error('Error release engine');
88
91
  }
89
92
  }
90
93
 
@@ -121,6 +124,7 @@ class Engine {
121
124
  this.isJoined = true;
122
125
  } catch (error) {
123
126
  this.logger.error('join()', error);
127
+ throw new Error(error);
124
128
  }
125
129
  }
126
130
 
@@ -146,7 +150,7 @@ class Engine {
146
150
  }
147
151
 
148
152
  if (!this.media.mediasoupDevice.canProduce('video')) {
149
- return;
153
+ throw new Error('Cant produce video');
150
154
  }
151
155
 
152
156
  if (!this.system.availableVideoDevices.length) {
@@ -210,6 +214,10 @@ class Engine {
210
214
  return;
211
215
  }
212
216
 
217
+ if (!this.media.mediasoupDevice.canProduce('audio')) {
218
+ throw new Error('Cant produce audio');
219
+ }
220
+
213
221
  const audioStream = await this.system.startAudioStream();
214
222
  this.system.setIsEnableAudioDevicesLock(true);
215
223
 
@@ -247,7 +255,6 @@ class Engine {
247
255
  this.logger.debug('publishAudio()');
248
256
  } catch (error) {
249
257
  this.logger.error('publishAudio()');
250
-
251
258
  throw new Error('Enable audio error');
252
259
  } finally {
253
260
  this.system.setIsEnableAudioDevicesLock(false);
@@ -7,7 +7,7 @@ class LoadBalancerApiClient {
7
7
 
8
8
  constructor() {
9
9
  this.api = axios.create({
10
- baseURL: 'https://lb.livedigital.space/v1',
10
+ baseURL: process.env.LIVEDIGITAL_APP_LOAD_BALANCER_BASE_URL,
11
11
  timeout: 5000,
12
12
  withCredentials: false,
13
13
  paramsSerializer: (params) => qs.stringify(params),
@@ -21,14 +21,11 @@ class System {
21
21
 
22
22
  public currentVideoDeviceId?: string;
23
23
 
24
- public getUserMediaError?: string = undefined;
25
-
26
- public deviceIsBusy?: boolean = false;
27
-
28
24
  private readonly logger: Logger;
29
25
 
30
26
  constructor() {
31
27
  this.logger = new Logger('System');
28
+ this.listenDevices();
32
29
  }
33
30
 
34
31
  setIsEnableVideoDevicesLock(value: boolean): void {
@@ -98,7 +95,19 @@ class System {
98
95
  };
99
96
  }
100
97
 
101
- async startVideoStream(params?: VideoConstraintParams): Promise<MediaStream | undefined> {
98
+ async getVideoTrack(): Promise<MediaStreamTrack> {
99
+ const videoStream = this.videoStream || await this.startVideoStream();
100
+ const [track] = videoStream.getVideoTracks();
101
+ return track;
102
+ }
103
+
104
+ async getAudioTrack(): Promise<MediaStreamTrack> {
105
+ const videoStream = this.videoStream || await this.startAudioStream();
106
+ const [track] = videoStream.getAudioTracks();
107
+ return track;
108
+ }
109
+
110
+ async startVideoStream(params?: VideoConstraintParams): Promise<MediaStream> {
102
111
  if (!this.videoStream || !this.videoStream.active) {
103
112
  try {
104
113
  this.videoStream = await navigator.mediaDevices.getUserMedia({
@@ -108,16 +117,12 @@ class System {
108
117
  this.logger.error('startVideoStream()');
109
118
 
110
119
  if (error.name === 'NotReadableError') {
111
- this.deviceIsBusy = true;
112
120
  this.videoStream = undefined;
113
-
114
- return undefined;
121
+ throw new Error('DeviceIsBusy');
115
122
  }
116
123
 
117
- if (error.name === 'NotAllowedError') {
118
- this.getUserMediaError = error.name;
119
-
120
- return undefined;
124
+ if (error.name === 'This action is not allowed') {
125
+ throw new Error('NotAllowedError');
121
126
  }
122
127
 
123
128
  throw new Error('Can`t start video stream');
@@ -127,7 +132,7 @@ class System {
127
132
  return this.videoStream;
128
133
  }
129
134
 
130
- async startAudioStream(params?: AudioConstraintParams): Promise<MediaStream | undefined> {
135
+ async startAudioStream(params?: AudioConstraintParams): Promise<MediaStream> {
131
136
  if (!this.audioStream || !this.audioStream.active) {
132
137
  try {
133
138
  this.audioStream = await navigator.mediaDevices.getUserMedia({
@@ -137,16 +142,12 @@ class System {
137
142
  this.logger.error('startAudioStream()', error);
138
143
 
139
144
  if (error.name === 'NotReadableError') {
140
- this.deviceIsBusy = true;
141
145
  this.audioStream = undefined;
142
-
143
- return undefined;
146
+ throw new Error('DeviceIsBusy');
144
147
  }
145
148
 
146
149
  if (error.name === 'NotAllowedError') {
147
- this.getUserMediaError = error.name;
148
-
149
- return undefined;
150
+ throw new Error('Device access denied');
150
151
  }
151
152
 
152
153
  throw new Error('Can`t start audio stream');
@@ -158,8 +159,6 @@ class System {
158
159
 
159
160
  async detectDevices(): Promise<void> {
160
161
  this.isDevicesLoaded = false;
161
- this.deviceIsBusy = false;
162
- this.getUserMediaError = undefined;
163
162
 
164
163
  try {
165
164
  const mediaDevices = await navigator.mediaDevices.enumerateDevices();
@@ -176,7 +175,7 @@ class System {
176
175
  });
177
176
  } catch (error) {
178
177
  if (error.name === 'NotReadableError') {
179
- this.deviceIsBusy = true;
178
+ throw new Error('DeviceIsBusy');
180
179
  }
181
180
  }
182
181
 
@@ -202,12 +201,19 @@ class System {
202
201
  this.setAvailableAudioDevices(audioDevices);
203
202
  this.isDevicesLoaded = true;
204
203
  } catch (error) {
205
- this.getUserMediaError = error.name || error;
206
204
  this.logger.error('detectDevices()');
207
205
  throw new Error('Can`t detect devices');
208
206
  }
209
207
  }
210
208
 
209
+ listenDevices(): void {
210
+ navigator.mediaDevices.ondevicechange = () => {
211
+ if (this.isDevicesLoaded) {
212
+ this.detectDevices();
213
+ }
214
+ };
215
+ }
216
+
211
217
  stopVideoStream(): void {
212
218
  if (!this.videoStream) {
213
219
  return;
package/src/index.ts CHANGED
@@ -43,7 +43,23 @@ class Client {
43
43
  return !!this.engine.media.audioProducer?.paused;
44
44
  }
45
45
 
46
- async getDevices(): Promise<AvailableMediaDevices> {
46
+ get availableVideoDevices(): MediaDeviceInfo[] {
47
+ return this.engine.system.availableVideoDevices;
48
+ }
49
+
50
+ get availableAudioDevices(): MediaDeviceInfo[] {
51
+ return this.engine.system.availableAudioDevices;
52
+ }
53
+
54
+ get currentAudioDeviceId(): string | undefined {
55
+ return this.engine.system.currentAudioDeviceId;
56
+ }
57
+
58
+ get currentVideoDeviceId(): string | undefined {
59
+ return this.engine.system.currentVideoDeviceId;
60
+ }
61
+
62
+ async detectDevices(): Promise<AvailableMediaDevices> {
47
63
  await this.engine.system.detectDevices();
48
64
  return {
49
65
  video: this.engine.system.availableVideoDevices,
@@ -51,6 +67,22 @@ class Client {
51
67
  };
52
68
  }
53
69
 
70
+ async getVideoStream(): Promise<MediaStream> {
71
+ return this.engine.system.videoStream || this.engine.system.startVideoStream();
72
+ }
73
+
74
+ async getAudioStream(): Promise<MediaStream> {
75
+ return this.engine.system.audioStream || this.engine.system.startAudioStream();
76
+ }
77
+
78
+ async getVideoTrack(): Promise<MediaStreamTrack> {
79
+ return this.engine.system.getVideoTrack();
80
+ }
81
+
82
+ async getAudioTrack(): Promise<MediaStreamTrack> {
83
+ return this.engine.system.getAudioTrack();
84
+ }
85
+
54
86
  setVideoDevice(deviceId: string): void {
55
87
  this.engine.system.setCurrentVideoDevice(deviceId);
56
88
  }