@livedigital/client 2.0.1 → 2.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/dist/constants/events.d.ts +2 -0
- package/dist/constants/simulcastEncodings.d.ts +2 -1
- package/dist/engine/Peer.d.ts +8 -16
- package/dist/engine/PeerConsumer.d.ts +37 -0
- package/dist/engine/PeerProducer.d.ts +5 -1
- package/dist/engine/media/tracks/BaseTrack.d.ts +3 -0
- package/dist/engine/media/tracks/PeerTrack.d.ts +29 -0
- package/dist/engine/media/tracks/VideoTrack.d.ts +4 -0
- package/dist/index.es.js +1 -1
- package/dist/index.js +1 -1
- package/dist/types/common.d.ts +26 -0
- package/package.json +2 -2
- package/src/constants/events.ts +2 -0
- package/src/constants/simulcastEncodings.ts +19 -5
- package/src/engine/Peer.ts +58 -172
- package/src/engine/PeerConsumer.ts +144 -0
- package/src/engine/PeerProducer.ts +11 -1
- package/src/engine/handlers/MediaSoupEventHandler.ts +80 -1
- package/src/engine/index.ts +47 -12
- package/src/engine/media/index.ts +8 -0
- package/src/engine/media/tracks/BaseTrack.ts +16 -0
- package/src/engine/media/tracks/PeerTrack.ts +214 -0
- package/src/engine/media/tracks/VideoTrack.ts +30 -0
- package/src/types/common.ts +32 -0
- package/dist/engine/media/Consumer.d.ts +0 -6
- package/dist/engine/media/VideoConsumer.d.ts +0 -14
- package/src/engine/media/Consumer.ts +0 -9
- package/src/engine/media/VideoConsumer.ts +0 -48
package/dist/types/common.d.ts
CHANGED
|
@@ -15,11 +15,24 @@ export declare type ProduceParams = {
|
|
|
15
15
|
rtpParameters: RtpParameters;
|
|
16
16
|
appData: Record<string, unknown>;
|
|
17
17
|
};
|
|
18
|
+
export declare type TrackTransformParams = {
|
|
19
|
+
width?: number;
|
|
20
|
+
height?: number;
|
|
21
|
+
};
|
|
18
22
|
export declare type ProducerData = {
|
|
19
23
|
id: string;
|
|
20
24
|
kind: MediaKind;
|
|
21
25
|
peerId: string;
|
|
22
26
|
label: TrackLabel;
|
|
27
|
+
encodings: RtpEncodingParameters[];
|
|
28
|
+
trackTransformParams: TrackTransformParams;
|
|
29
|
+
maxSpatialLayer: number;
|
|
30
|
+
};
|
|
31
|
+
export declare type ConsumerData = {
|
|
32
|
+
producerData: ProducerData;
|
|
33
|
+
peerId: string;
|
|
34
|
+
appId: string;
|
|
35
|
+
channelId: string;
|
|
23
36
|
};
|
|
24
37
|
export declare type Role = 'audience' | 'host';
|
|
25
38
|
export declare type PeerResponse = {
|
|
@@ -185,4 +198,17 @@ export declare type CreateVideoTrackParams = {
|
|
|
185
198
|
};
|
|
186
199
|
};
|
|
187
200
|
encoderConfig: VideoEncoderConfig;
|
|
201
|
+
transformParams: TransformParams;
|
|
202
|
+
};
|
|
203
|
+
export declare type SpatialLayerParams = RtpEncodingParameters & TransformParams;
|
|
204
|
+
export declare type ProducerRequestMaxSpatialLayer = {
|
|
205
|
+
producerId: string;
|
|
206
|
+
spatialLayer: number;
|
|
207
|
+
};
|
|
208
|
+
export declare type ProducerSetMaxSpatialLayer = ProducerRequestMaxSpatialLayer & {
|
|
209
|
+
peerId: string;
|
|
210
|
+
};
|
|
211
|
+
export declare type TransformParams = {
|
|
212
|
+
width?: number;
|
|
213
|
+
height?: number;
|
|
188
214
|
};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@livedigital/client",
|
|
3
3
|
"author": "vlprojects",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "2.0
|
|
5
|
+
"version": "2.2.0",
|
|
6
6
|
"private": false,
|
|
7
7
|
"bugs": {
|
|
8
8
|
"url": "https://github.com/vlprojects/livedigital-sdk/issues"
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"axios": "^0.21.4",
|
|
45
45
|
"debug": "^4.3.1",
|
|
46
|
-
"mediasoup-client": "^3.6.
|
|
46
|
+
"mediasoup-client": "^3.6.50",
|
|
47
47
|
"qs": "^6.9.6",
|
|
48
48
|
"serialize-error": "^7.0.1",
|
|
49
49
|
"socket.io-client": "^4.3.0"
|
package/src/constants/events.ts
CHANGED
|
@@ -36,6 +36,8 @@ export const MEDIASOUP_EVENTS = {
|
|
|
36
36
|
pauseProducer: 'producer.pause',
|
|
37
37
|
resumeProducer: 'producer.resume',
|
|
38
38
|
producerScoreChanged: 'producer.scoreChanged',
|
|
39
|
+
producerSetMaxSpatialLayer: 'producer.setMaxSpatialLayer',
|
|
40
|
+
producerRequestMaxSpatialLayer: 'producer.requestMaxSpatialLayer',
|
|
39
41
|
createConsumer: 'consumer.create',
|
|
40
42
|
closeConsumer: 'consumer.close',
|
|
41
43
|
pauseConsumer: 'consumer.pause',
|
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
export const WEBCAM_SIMULCAST_ENCODINGS = [
|
|
2
|
-
{
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
{
|
|
3
|
+
scaleResolutionDownBy: 4, // 320x180 0.15mbps 12fps by default
|
|
4
|
+
maxBitrate: 150000,
|
|
5
|
+
maxFramerate: 12,
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
scaleResolutionDownBy: 2, // 640x360 0.35mbps 18fps by default
|
|
9
|
+
maxBitrate: 350000,
|
|
10
|
+
maxFramerate: 18,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
scaleResolutionDownBy: 1, // 1280x720 0.9mbps 24fps by default
|
|
14
|
+
maxBitrate: 900000,
|
|
15
|
+
maxFramerate: 24,
|
|
16
|
+
},
|
|
5
17
|
];
|
|
6
18
|
|
|
7
19
|
export const SCREEN_SHARING_SIMULCAST_ENCODINGS = [
|
|
8
|
-
{
|
|
9
|
-
|
|
20
|
+
{
|
|
21
|
+
maxBitrate: 1000000, // 1920x1080 1mbps 30fps by default
|
|
22
|
+
dtx: true,
|
|
23
|
+
},
|
|
10
24
|
];
|
package/src/engine/Peer.ts
CHANGED
|
@@ -1,34 +1,30 @@
|
|
|
1
1
|
import { ConsumerOptions } from 'mediasoup-client/lib/Consumer';
|
|
2
2
|
import {
|
|
3
3
|
ConsumerScoreChangedPayload,
|
|
4
|
-
PreferredLayersParams,
|
|
5
4
|
ProducerData,
|
|
6
5
|
ProducerScoreChangedPayload,
|
|
7
|
-
SetConsumerPriorityParams,
|
|
8
|
-
SocketResponse,
|
|
9
6
|
ConnectionQuality,
|
|
10
|
-
StartTrackPayload,
|
|
11
7
|
EndTrackPayload,
|
|
12
8
|
PayloadOfPublishedMedia,
|
|
13
9
|
PayloadOfUnpublishedMedia,
|
|
14
10
|
ChangePreferredLayersPayload,
|
|
15
11
|
Role,
|
|
16
12
|
} from '../types/common';
|
|
17
|
-
import Consumer from './media/Consumer';
|
|
18
|
-
import VideoConsumer from './media/VideoConsumer';
|
|
19
13
|
import EnhancedEventEmitter from '../EnhancedEventEmitter';
|
|
20
14
|
import Engine from './index';
|
|
21
15
|
import { MEDIASOUP_EVENTS, PEER_EVENTS } from '../constants/events';
|
|
22
16
|
import Logger from './Logger';
|
|
23
17
|
import PeerProducer from './PeerProducer';
|
|
18
|
+
import PeerConsumer from './PeerConsumer';
|
|
19
|
+
import PeerTrack from './media/tracks/PeerTrack';
|
|
24
20
|
|
|
25
21
|
interface PeerConstructor {
|
|
26
22
|
id: string;
|
|
27
23
|
channelIds: string[];
|
|
28
24
|
appId: string;
|
|
29
25
|
producers: ProducerData[],
|
|
30
|
-
videoConsumer?:
|
|
31
|
-
audioConsumer?:
|
|
26
|
+
videoConsumer?: PeerConsumer;
|
|
27
|
+
audioConsumer?: PeerConsumer;
|
|
32
28
|
engine: Engine;
|
|
33
29
|
soundLevel?: number;
|
|
34
30
|
loginDate: Date;
|
|
@@ -60,7 +56,7 @@ class Peer {
|
|
|
60
56
|
|
|
61
57
|
private producers: Map<string, PeerProducer> = new Map();
|
|
62
58
|
|
|
63
|
-
|
|
59
|
+
public readonly tracks: Map<string, PeerTrack> = new Map();
|
|
64
60
|
|
|
65
61
|
private readonly engine: Engine;
|
|
66
62
|
|
|
@@ -128,105 +124,6 @@ class Peer {
|
|
|
128
124
|
await this.createConsumer(producer);
|
|
129
125
|
}
|
|
130
126
|
|
|
131
|
-
public async setMinResolution(consumerId: string): Promise<void> {
|
|
132
|
-
const consumer = this.consumers.get(consumerId);
|
|
133
|
-
if (!(consumer instanceof VideoConsumer) || consumer.spatialLayers === 0) {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
await this.changeConsumerPreferredLayers(consumer, { spatialLayer: 0 });
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
public async setMediumResolution(consumerId: string): Promise<void> {
|
|
141
|
-
const consumer = this.consumers.get(consumerId);
|
|
142
|
-
if (!(consumer instanceof VideoConsumer) || consumer.spatialLayers === 0) {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const mediumLayer = consumer.spatialLayers > 2
|
|
147
|
-
? consumer.spatialLayers - 2
|
|
148
|
-
: consumer.spatialLayers;
|
|
149
|
-
await this.changeConsumerPreferredLayers(consumer, { spatialLayer: mediumLayer });
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
public async setMaxResolution(consumerId: string): Promise<void> {
|
|
153
|
-
const consumer = this.consumers.get(consumerId);
|
|
154
|
-
if (!(consumer instanceof VideoConsumer) || consumer.spatialLayers === 0) {
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
await this.changeConsumerPreferredLayers(consumer, {
|
|
159
|
-
spatialLayer: consumer.spatialLayers - 1,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
public async pause(consumerId: string): Promise<void> {
|
|
164
|
-
const consumer = this.consumers.get(consumerId);
|
|
165
|
-
if (!consumer) {
|
|
166
|
-
this.logger.warn('pause()', { message: 'This peer has no consumer to pause', peer: this, consumerId });
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (consumer.paused) {
|
|
171
|
-
this.logger.warn('pause()', { message: 'Already paused', peer: this, consumerId });
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
await this.pauseRemoteConsumer(consumer.id);
|
|
177
|
-
consumer.pause();
|
|
178
|
-
this.logger.debug('pause()', { peer: this, consumerId });
|
|
179
|
-
} catch (err) {
|
|
180
|
-
this.logger.error('pause()', err);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
public async resume(consumerId: string): Promise<void> {
|
|
185
|
-
const consumer = this.consumers.get(consumerId);
|
|
186
|
-
if (!consumer) {
|
|
187
|
-
this.logger.warn('resume()', { message: 'This peer has no consumer to resume', peer: this, consumerId });
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (!consumer.paused) {
|
|
192
|
-
this.logger.warn('resume()', { message: 'Already playing', peer: this, consumerId });
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
try {
|
|
197
|
-
await this.resumeRemoteConsumer(consumer.id);
|
|
198
|
-
consumer.resume();
|
|
199
|
-
this.logger.debug('resume()', { peer: this });
|
|
200
|
-
} catch (err) {
|
|
201
|
-
this.logger.error('resume()', err);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
public async setPriority(consumerId: string, priority: number): Promise<void> {
|
|
206
|
-
const consumer = this.consumers.get(consumerId);
|
|
207
|
-
if (!consumer) {
|
|
208
|
-
this.logger.warn('setPriority()', {
|
|
209
|
-
message: 'This peer has no consumer to change priority',
|
|
210
|
-
peer: this,
|
|
211
|
-
consumerId,
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
await this.setConsumerPriority({ consumerId, priority });
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
private async setConsumerPriority(params: SetConsumerPriorityParams): Promise<void> {
|
|
221
|
-
try {
|
|
222
|
-
await this.engine.network.socket.request(MEDIASOUP_EVENTS.setConsumerPriority, params);
|
|
223
|
-
this.logger.debug('setConsumerPriority()', { peer: this, params });
|
|
224
|
-
} catch (err) {
|
|
225
|
-
this.logger.error('setConsumerPriority()', { peer: this, params });
|
|
226
|
-
throw new Error('Can`t change stream priority');
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
127
|
private async createConsumer(producer: PeerProducer): Promise<void> {
|
|
231
128
|
const transport = this.engine.network.receiveTransport;
|
|
232
129
|
if (!transport) {
|
|
@@ -250,62 +147,21 @@ class Peer {
|
|
|
250
147
|
const consumer = await transport.consume(remoteConsumer);
|
|
251
148
|
await this.engine.network.socket.request(MEDIASOUP_EVENTS.resumeConsumer, { consumerId: consumer.id });
|
|
252
149
|
|
|
253
|
-
if (producer.kind === 'audio') {
|
|
254
|
-
this.consumers.set(consumer.id, new Consumer(consumer));
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (producer.kind === 'video') {
|
|
258
|
-
this.consumers.set(consumer.id, new VideoConsumer(consumer));
|
|
259
|
-
}
|
|
260
|
-
|
|
261
150
|
this.logger.debug(`Subscribed for ${producer.kind}`, { peer: this });
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
consumerId: consumer.id,
|
|
265
|
-
track: consumer.track,
|
|
151
|
+
const track = new PeerTrack({
|
|
152
|
+
mediaStreamTrack: consumer.track,
|
|
266
153
|
label: producer.label,
|
|
267
|
-
|
|
154
|
+
consumer: new PeerConsumer(consumer),
|
|
155
|
+
engine: this.engine,
|
|
156
|
+
});
|
|
157
|
+
this.tracks.set(track.label, track);
|
|
158
|
+
this.observer.safeEmit(PEER_EVENTS.trackStart, track);
|
|
268
159
|
} catch (error) {
|
|
269
160
|
this.logger.error('createConsumer()', producer, error);
|
|
270
161
|
throw new Error('Error subscribe media');
|
|
271
162
|
}
|
|
272
163
|
}
|
|
273
164
|
|
|
274
|
-
private async changeConsumerPreferredLayers(
|
|
275
|
-
consumer: VideoConsumer,
|
|
276
|
-
{ spatialLayer, temporalLayer }: PreferredLayersParams,
|
|
277
|
-
): Promise<void> {
|
|
278
|
-
try {
|
|
279
|
-
await this.engine.network.socket.request(MEDIASOUP_EVENTS.consumerChangePreferredLayers, {
|
|
280
|
-
consumerId: consumer.id,
|
|
281
|
-
spatialLayer,
|
|
282
|
-
temporalLayer,
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
this.logger.debug('changeConsumerPreferredLayers()', {
|
|
286
|
-
peer: this,
|
|
287
|
-
consumer,
|
|
288
|
-
spatialLayer,
|
|
289
|
-
temporalLayer,
|
|
290
|
-
});
|
|
291
|
-
} catch (err) {
|
|
292
|
-
this.logger.error('changeConsumerPreferredLayers()', {
|
|
293
|
-
peer: this,
|
|
294
|
-
consumer,
|
|
295
|
-
error: err,
|
|
296
|
-
});
|
|
297
|
-
throw new Error('Error change preferred layer');
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
private async pauseRemoteConsumer(consumerId: string): Promise<SocketResponse> {
|
|
302
|
-
return this.engine.network.socket.request(MEDIASOUP_EVENTS.pauseConsumer, { consumerId });
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
private async resumeRemoteConsumer(consumerId: string): Promise<SocketResponse> {
|
|
306
|
-
return this.engine.network.socket.request(MEDIASOUP_EVENTS.resumeConsumer, { consumerId });
|
|
307
|
-
}
|
|
308
|
-
|
|
309
165
|
private handleNewProducer(producerData: ProducerData): void {
|
|
310
166
|
if (this.producers.get(producerData.id)) {
|
|
311
167
|
return;
|
|
@@ -335,13 +191,13 @@ class Peer {
|
|
|
335
191
|
});
|
|
336
192
|
|
|
337
193
|
this.observer.on(MEDIASOUP_EVENTS.closeConsumer, (consumerId) => {
|
|
338
|
-
const consumer = this.
|
|
194
|
+
const consumer = this.getConsumerById(consumerId);
|
|
339
195
|
if (!consumer) {
|
|
340
196
|
return;
|
|
341
197
|
}
|
|
342
198
|
|
|
343
199
|
consumer.close();
|
|
344
|
-
this.
|
|
200
|
+
this.tracks.delete(consumer.appData.producerData.label);
|
|
345
201
|
this.observer.safeEmit(PEER_EVENTS.trackEnd, {
|
|
346
202
|
producerId: consumer.producerId,
|
|
347
203
|
kind: consumer.kind,
|
|
@@ -351,7 +207,7 @@ class Peer {
|
|
|
351
207
|
});
|
|
352
208
|
|
|
353
209
|
this.observer.on(MEDIASOUP_EVENTS.pauseConsumer, (consumerId) => {
|
|
354
|
-
const consumer = this.
|
|
210
|
+
const consumer = this.getConsumerById(consumerId);
|
|
355
211
|
if (!consumer) {
|
|
356
212
|
return;
|
|
357
213
|
}
|
|
@@ -361,7 +217,7 @@ class Peer {
|
|
|
361
217
|
});
|
|
362
218
|
|
|
363
219
|
this.observer.on(MEDIASOUP_EVENTS.resumeConsumer, (consumerId) => {
|
|
364
|
-
const consumer = this.
|
|
220
|
+
const consumer = this.getConsumerById(consumerId);
|
|
365
221
|
if (!consumer) {
|
|
366
222
|
return;
|
|
367
223
|
}
|
|
@@ -376,7 +232,7 @@ class Peer {
|
|
|
376
232
|
return;
|
|
377
233
|
}
|
|
378
234
|
|
|
379
|
-
const consumer = this.
|
|
235
|
+
const consumer = this.getConsumerById(payload.consumerId);
|
|
380
236
|
if (!consumer) {
|
|
381
237
|
return;
|
|
382
238
|
}
|
|
@@ -426,6 +282,12 @@ class Peer {
|
|
|
426
282
|
return;
|
|
427
283
|
}
|
|
428
284
|
|
|
285
|
+
const track = this.engine.media.getAllTracks().find((t) => t.getProducer()?.id === payload.producerId);
|
|
286
|
+
if (!track) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const maxSpatialLayer = track.getProducer()?.maxSpatialLayer;
|
|
429
291
|
const { scores } = payload;
|
|
430
292
|
if (scores.length === 0) {
|
|
431
293
|
return;
|
|
@@ -433,13 +295,18 @@ class Peer {
|
|
|
433
295
|
|
|
434
296
|
let { score } = scores[0];
|
|
435
297
|
|
|
436
|
-
if (scores.length > 1) {
|
|
298
|
+
if (scores.length > 1 && maxSpatialLayer !== 0) {
|
|
299
|
+
// don't count disabled layers if they were disabled recently
|
|
300
|
+
if (maxSpatialLayer) {
|
|
301
|
+
scores.splice(maxSpatialLayer + 1);
|
|
302
|
+
}
|
|
303
|
+
|
|
437
304
|
// video with multiple encodings has personal score for each encoding
|
|
438
305
|
const scoresSum = scores
|
|
439
306
|
.map(({ score: currentScore }) => currentScore)
|
|
440
307
|
.reduce((acc, curr) => acc + curr, 0);
|
|
441
308
|
|
|
442
|
-
score = Math.floor(scoresSum /
|
|
309
|
+
score = Math.floor(scoresSum / scores.length);
|
|
443
310
|
}
|
|
444
311
|
|
|
445
312
|
this.outgoingConnectionQuality = Peer.getConnectionQualityByScore(score);
|
|
@@ -447,21 +314,30 @@ class Peer {
|
|
|
447
314
|
});
|
|
448
315
|
|
|
449
316
|
this.observer.on(MEDIASOUP_EVENTS.consumerChangePreferredLayers, (payload: ChangePreferredLayersPayload) => {
|
|
450
|
-
const consumer = this.
|
|
451
|
-
if (consumer
|
|
317
|
+
const consumer = this.getConsumerById(payload.consumerId);
|
|
318
|
+
if (consumer?.isVideo) {
|
|
452
319
|
consumer.setCurrentTemporalLayer(payload.temporalLayer);
|
|
453
320
|
consumer.setCurrentSpatialLayer(payload.spatialLayer);
|
|
321
|
+
if (payload.spatialLayer === consumer.requestedSpatialLayer) {
|
|
322
|
+
consumer.setRequestedSpatialLayer(undefined);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
this.logger.debug('consumerChangePreferredLayers()', { consumer, ...payload });
|
|
454
326
|
}
|
|
455
327
|
});
|
|
456
328
|
}
|
|
457
329
|
|
|
458
330
|
private emitConnectionQuality(): void {
|
|
459
|
-
//
|
|
460
|
-
|
|
331
|
+
// after the initialization a new layer, or new producer, scores of producers and consumers drop for a short time
|
|
332
|
+
setTimeout(() => {
|
|
333
|
+
// pick connection (incoming or outgoing) with lower quality
|
|
334
|
+
const connectionQuality = Math.min(this.outgoingConnectionQuality, this.incomingConnectionQuality);
|
|
335
|
+
this.observer.safeEmit(PEER_EVENTS.connectionQualityChanged, {
|
|
336
|
+
connectionQuality,
|
|
337
|
+
});
|
|
461
338
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
});
|
|
339
|
+
this.logger.debug('emitConnectionQuality()', { peer: this, connectionQuality });
|
|
340
|
+
}, 1000);
|
|
465
341
|
}
|
|
466
342
|
|
|
467
343
|
static getConnectionQualityByScore(score: number): ConnectionQuality {
|
|
@@ -476,13 +352,23 @@ class Peer {
|
|
|
476
352
|
return ConnectionQuality.GOOD;
|
|
477
353
|
}
|
|
478
354
|
|
|
479
|
-
|
|
480
|
-
return Array.from(this.
|
|
355
|
+
private getAllConsumers(): PeerConsumer[] {
|
|
356
|
+
return Array.from(this.tracks.values())
|
|
357
|
+
.map((track) => track.consumer)
|
|
358
|
+
.filter((item): item is PeerConsumer => !!item);
|
|
481
359
|
}
|
|
482
360
|
|
|
483
361
|
public getAllProducers(): PeerProducer[] {
|
|
484
362
|
return Array.from(this.producers.values());
|
|
485
363
|
}
|
|
364
|
+
|
|
365
|
+
public getConsumerByProducerId(producerId: string): PeerConsumer | undefined {
|
|
366
|
+
return this.getAllConsumers().find((consumer) => consumer.producerId === producerId);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
public getConsumerById(id: string): PeerConsumer | undefined {
|
|
370
|
+
return this.getAllConsumers().find((consumer) => consumer.id === id);
|
|
371
|
+
}
|
|
486
372
|
}
|
|
487
373
|
|
|
488
374
|
export default Peer;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { parseScalabilityMode } from 'mediasoup-client';
|
|
2
|
+
import { MediaKind, RtpEncodingParameters } from 'mediasoup-client/lib/RtpParameters';
|
|
3
|
+
import { Consumer as MediasoupConsumer } from 'mediasoup-client/lib/types';
|
|
4
|
+
import { ConsumerData, SpatialLayerParams } from '../types/common';
|
|
5
|
+
import Logger from './Logger';
|
|
6
|
+
|
|
7
|
+
class PeerConsumer {
|
|
8
|
+
public score = 10;
|
|
9
|
+
|
|
10
|
+
public producerScore = 10;
|
|
11
|
+
|
|
12
|
+
public spatialLayers = 0;
|
|
13
|
+
|
|
14
|
+
public temporalLayers = 0;
|
|
15
|
+
|
|
16
|
+
public currentSpatialLayer = 0;
|
|
17
|
+
|
|
18
|
+
public currentTemporalLayer = 0;
|
|
19
|
+
|
|
20
|
+
public requestedSpatialLayer?: number;
|
|
21
|
+
|
|
22
|
+
public currentMaxSpatialLayer: number;
|
|
23
|
+
|
|
24
|
+
public availableSpatialLayers: SpatialLayerParams[] = [];
|
|
25
|
+
|
|
26
|
+
public currentSpatialLayerParams?: SpatialLayerParams;
|
|
27
|
+
|
|
28
|
+
readonly id: string;
|
|
29
|
+
|
|
30
|
+
readonly kind: MediaKind;
|
|
31
|
+
|
|
32
|
+
readonly producerId: string;
|
|
33
|
+
|
|
34
|
+
readonly appData: ConsumerData;
|
|
35
|
+
|
|
36
|
+
private readonly consumer: MediasoupConsumer;
|
|
37
|
+
|
|
38
|
+
private readonly logger: Logger;
|
|
39
|
+
|
|
40
|
+
constructor(consumer: MediasoupConsumer) {
|
|
41
|
+
this.id = consumer.id;
|
|
42
|
+
this.kind = consumer.kind as MediaKind;
|
|
43
|
+
this.producerId = consumer.producerId;
|
|
44
|
+
this.appData = consumer.appData as ConsumerData;
|
|
45
|
+
this.consumer = consumer;
|
|
46
|
+
this.logger = new Logger('PeerConsumer');
|
|
47
|
+
this.currentMaxSpatialLayer = this.appData.producerData.maxSpatialLayer;
|
|
48
|
+
this.parseScalabilityMode();
|
|
49
|
+
this.setSpatialLayersParams();
|
|
50
|
+
this.setCurrentSpatialLayerParams();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get paused() {
|
|
54
|
+
return this.consumer.paused;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pause() {
|
|
58
|
+
return this.consumer.pause();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
resume() {
|
|
62
|
+
return this.consumer.resume();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
close() {
|
|
66
|
+
return this.consumer.close();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get isVideo(): boolean {
|
|
70
|
+
return this.kind === 'video';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
get isAudio(): boolean {
|
|
74
|
+
return this.kind === 'audio';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
parseScalabilityMode(): void {
|
|
78
|
+
if (this.consumer.rtpParameters.encodings) {
|
|
79
|
+
const { scalabilityMode } = this.consumer.rtpParameters.encodings[0];
|
|
80
|
+
const { spatialLayers, temporalLayers } = parseScalabilityMode(scalabilityMode);
|
|
81
|
+
this.spatialLayers = spatialLayers;
|
|
82
|
+
this.temporalLayers = temporalLayers;
|
|
83
|
+
this.logger.debug('parseScalabilityMode()', {
|
|
84
|
+
scalabilityMode,
|
|
85
|
+
spatialLayers,
|
|
86
|
+
temporalLayers,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
setSpatialLayersParams(): void {
|
|
92
|
+
const { encodings } = this.appData.producerData;
|
|
93
|
+
this.availableSpatialLayers = encodings.map((encoding) => this.parseSpatialLayerParams(encoding));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
setCurrentSpatialLayerParams(): void {
|
|
97
|
+
const { encodings } = this.appData.producerData;
|
|
98
|
+
this.currentSpatialLayerParams = this.parseSpatialLayerParams(encodings[this.currentSpatialLayer]);
|
|
99
|
+
this.logger.debug('setCurrentSpatialLayerParams()', { currentSpatialLayerParams: this.currentSpatialLayerParams });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
parseSpatialLayerParams(encoding: RtpEncodingParameters): SpatialLayerParams {
|
|
103
|
+
const { trackTransformParams: { width, height } } = this.appData.producerData;
|
|
104
|
+
const coefficient = encoding.scaleResolutionDownBy || 1;
|
|
105
|
+
if (width && height) {
|
|
106
|
+
return {
|
|
107
|
+
width: width / coefficient,
|
|
108
|
+
height: height / coefficient,
|
|
109
|
+
...encoding,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return encoding;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
setCurrentSpatialLayer(currentSpatialLayer: number): void {
|
|
117
|
+
if (!this.isVideo) {
|
|
118
|
+
throw new Error('setCurrentSpatialLayer applies to video consumers only');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.currentSpatialLayer = currentSpatialLayer;
|
|
122
|
+
this.logger.debug('setCurrentSpatialLayer()', { currentSpatialLayer });
|
|
123
|
+
this.setCurrentSpatialLayerParams();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
setCurrentTemporalLayer(currentTemporalLayer: number): void {
|
|
127
|
+
if (!this.isVideo) {
|
|
128
|
+
throw new Error('setCurrentTemporalLayer applies to video consumers only');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.currentTemporalLayer = currentTemporalLayer;
|
|
132
|
+
this.logger.debug('setCurrentTemporalLayer()', { currentTemporalLayer });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
setRequestedSpatialLayer(spatialLayer: number | undefined): void {
|
|
136
|
+
this.requestedSpatialLayer = spatialLayer;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
setCurrentMaxSpatialLayer(spatialLayer: number): void {
|
|
140
|
+
this.currentMaxSpatialLayer = spatialLayer;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default PeerConsumer;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MediaKind } from 'mediasoup-client/lib/RtpParameters';
|
|
2
|
-
import {
|
|
2
|
+
import { RtpEncodingParameters } from 'mediasoup-client/lib/types';
|
|
3
|
+
import { ProducerData, TrackLabel, TrackTransformParams } from '../types/common';
|
|
3
4
|
|
|
4
5
|
class PeerProducer {
|
|
5
6
|
readonly id: string;
|
|
@@ -10,6 +11,12 @@ class PeerProducer {
|
|
|
10
11
|
|
|
11
12
|
readonly label: TrackLabel;
|
|
12
13
|
|
|
14
|
+
readonly encodings: RtpEncodingParameters[];
|
|
15
|
+
|
|
16
|
+
readonly trackTransformParams: TrackTransformParams;
|
|
17
|
+
|
|
18
|
+
readonly maxSpatialLayer: number;
|
|
19
|
+
|
|
13
20
|
public score = 10;
|
|
14
21
|
|
|
15
22
|
constructor(params: ProducerData) {
|
|
@@ -21,6 +28,9 @@ class PeerProducer {
|
|
|
21
28
|
this.kind = kind;
|
|
22
29
|
this.peerId = peerId;
|
|
23
30
|
this.label = label;
|
|
31
|
+
this.encodings = params.encodings;
|
|
32
|
+
this.trackTransformParams = params.trackTransformParams;
|
|
33
|
+
this.maxSpatialLayer = params.maxSpatialLayer;
|
|
24
34
|
}
|
|
25
35
|
}
|
|
26
36
|
|