@livedigital/client 2.1.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 +5 -12
- package/dist/engine/PeerConsumer.d.ts +11 -2
- 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 +20 -0
- package/package.json +1 -1
- package/src/constants/events.ts +2 -0
- package/src/constants/simulcastEncodings.ts +19 -5
- package/src/engine/Peer.ts +54 -161
- package/src/engine/PeerConsumer.ts +50 -4
- 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 +25 -0
package/dist/types/common.d.ts
CHANGED
|
@@ -15,11 +15,18 @@ 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;
|
|
23
30
|
};
|
|
24
31
|
export declare type ConsumerData = {
|
|
25
32
|
producerData: ProducerData;
|
|
@@ -191,4 +198,17 @@ export declare type CreateVideoTrackParams = {
|
|
|
191
198
|
};
|
|
192
199
|
};
|
|
193
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;
|
|
194
214
|
};
|
package/package.json
CHANGED
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,13 +1,9 @@
|
|
|
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,
|
|
@@ -20,6 +16,7 @@ import { MEDIASOUP_EVENTS, PEER_EVENTS } from '../constants/events';
|
|
|
20
16
|
import Logger from './Logger';
|
|
21
17
|
import PeerProducer from './PeerProducer';
|
|
22
18
|
import PeerConsumer from './PeerConsumer';
|
|
19
|
+
import PeerTrack from './media/tracks/PeerTrack';
|
|
23
20
|
|
|
24
21
|
interface PeerConstructor {
|
|
25
22
|
id: string;
|
|
@@ -59,7 +56,7 @@ class Peer {
|
|
|
59
56
|
|
|
60
57
|
private producers: Map<string, PeerProducer> = new Map();
|
|
61
58
|
|
|
62
|
-
|
|
59
|
+
public readonly tracks: Map<string, PeerTrack> = new Map();
|
|
63
60
|
|
|
64
61
|
private readonly engine: Engine;
|
|
65
62
|
|
|
@@ -127,105 +124,6 @@ class Peer {
|
|
|
127
124
|
await this.createConsumer(producer);
|
|
128
125
|
}
|
|
129
126
|
|
|
130
|
-
public async setMinResolution(consumerId: string): Promise<void> {
|
|
131
|
-
const consumer = this.consumers.get(consumerId);
|
|
132
|
-
if (!consumer?.isVideo || consumer.spatialLayers === 0) {
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
await this.changeConsumerPreferredLayers(consumer, { spatialLayer: 0 });
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
public async setMediumResolution(consumerId: string): Promise<void> {
|
|
140
|
-
const consumer = this.consumers.get(consumerId);
|
|
141
|
-
if (!consumer?.isVideo || consumer.spatialLayers === 0) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const mediumLayer = consumer.spatialLayers > 2
|
|
146
|
-
? consumer.spatialLayers - 2
|
|
147
|
-
: consumer.spatialLayers;
|
|
148
|
-
await this.changeConsumerPreferredLayers(consumer, { spatialLayer: mediumLayer });
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
public async setMaxResolution(consumerId: string): Promise<void> {
|
|
152
|
-
const consumer = this.consumers.get(consumerId);
|
|
153
|
-
if (!consumer?.isVideo || consumer.spatialLayers === 0) {
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
await this.changeConsumerPreferredLayers(consumer, {
|
|
158
|
-
spatialLayer: consumer.spatialLayers - 1,
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
public async pause(consumerId: string): Promise<void> {
|
|
163
|
-
const consumer = this.consumers.get(consumerId);
|
|
164
|
-
if (!consumer) {
|
|
165
|
-
this.logger.warn('pause()', { message: 'This peer has no consumer to pause', peer: this, consumerId });
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (consumer.paused) {
|
|
170
|
-
this.logger.warn('pause()', { message: 'Already paused', peer: this, consumerId });
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
try {
|
|
175
|
-
await this.pauseRemoteConsumer(consumer.id);
|
|
176
|
-
consumer.pause();
|
|
177
|
-
this.logger.debug('pause()', { peer: this, consumerId });
|
|
178
|
-
} catch (err) {
|
|
179
|
-
this.logger.error('pause()', err);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
public async resume(consumerId: string): Promise<void> {
|
|
184
|
-
const consumer = this.consumers.get(consumerId);
|
|
185
|
-
if (!consumer) {
|
|
186
|
-
this.logger.warn('resume()', { message: 'This peer has no consumer to resume', peer: this, consumerId });
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (!consumer.paused) {
|
|
191
|
-
this.logger.warn('resume()', { message: 'Already playing', peer: this, consumerId });
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
try {
|
|
196
|
-
await this.resumeRemoteConsumer(consumer.id);
|
|
197
|
-
consumer.resume();
|
|
198
|
-
this.logger.debug('resume()', { peer: this });
|
|
199
|
-
} catch (err) {
|
|
200
|
-
this.logger.error('resume()', err);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
public async setPriority(consumerId: string, priority: number): Promise<void> {
|
|
205
|
-
const consumer = this.consumers.get(consumerId);
|
|
206
|
-
if (!consumer) {
|
|
207
|
-
this.logger.warn('setPriority()', {
|
|
208
|
-
message: 'This peer has no consumer to change priority',
|
|
209
|
-
peer: this,
|
|
210
|
-
consumerId,
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
await this.setConsumerPriority({ consumerId, priority });
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
private async setConsumerPriority(params: SetConsumerPriorityParams): Promise<void> {
|
|
220
|
-
try {
|
|
221
|
-
await this.engine.network.socket.request(MEDIASOUP_EVENTS.setConsumerPriority, params);
|
|
222
|
-
this.logger.debug('setConsumerPriority()', { peer: this, params });
|
|
223
|
-
} catch (err) {
|
|
224
|
-
this.logger.error('setConsumerPriority()', { peer: this, params });
|
|
225
|
-
throw new Error('Can`t change stream priority');
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
127
|
private async createConsumer(producer: PeerProducer): Promise<void> {
|
|
230
128
|
const transport = this.engine.network.receiveTransport;
|
|
231
129
|
if (!transport) {
|
|
@@ -249,56 +147,21 @@ class Peer {
|
|
|
249
147
|
const consumer = await transport.consume(remoteConsumer);
|
|
250
148
|
await this.engine.network.socket.request(MEDIASOUP_EVENTS.resumeConsumer, { consumerId: consumer.id });
|
|
251
149
|
|
|
252
|
-
this.consumers.set(consumer.id, new PeerConsumer(consumer));
|
|
253
|
-
|
|
254
150
|
this.logger.debug(`Subscribed for ${producer.kind}`, { peer: this });
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
consumerId: consumer.id,
|
|
258
|
-
track: consumer.track,
|
|
151
|
+
const track = new PeerTrack({
|
|
152
|
+
mediaStreamTrack: consumer.track,
|
|
259
153
|
label: producer.label,
|
|
260
|
-
|
|
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);
|
|
261
159
|
} catch (error) {
|
|
262
160
|
this.logger.error('createConsumer()', producer, error);
|
|
263
161
|
throw new Error('Error subscribe media');
|
|
264
162
|
}
|
|
265
163
|
}
|
|
266
164
|
|
|
267
|
-
private async changeConsumerPreferredLayers(
|
|
268
|
-
consumer: PeerConsumer,
|
|
269
|
-
{ spatialLayer, temporalLayer }: PreferredLayersParams,
|
|
270
|
-
): Promise<void> {
|
|
271
|
-
try {
|
|
272
|
-
await this.engine.network.socket.request(MEDIASOUP_EVENTS.consumerChangePreferredLayers, {
|
|
273
|
-
consumerId: consumer.id,
|
|
274
|
-
spatialLayer,
|
|
275
|
-
temporalLayer,
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
this.logger.debug('changeConsumerPreferredLayers()', {
|
|
279
|
-
peer: this,
|
|
280
|
-
consumer,
|
|
281
|
-
spatialLayer,
|
|
282
|
-
temporalLayer,
|
|
283
|
-
});
|
|
284
|
-
} catch (err) {
|
|
285
|
-
this.logger.error('changeConsumerPreferredLayers()', {
|
|
286
|
-
peer: this,
|
|
287
|
-
consumer,
|
|
288
|
-
error: err,
|
|
289
|
-
});
|
|
290
|
-
throw new Error('Error change preferred layer');
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
private async pauseRemoteConsumer(consumerId: string): Promise<SocketResponse> {
|
|
295
|
-
return this.engine.network.socket.request(MEDIASOUP_EVENTS.pauseConsumer, { consumerId });
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
private async resumeRemoteConsumer(consumerId: string): Promise<SocketResponse> {
|
|
299
|
-
return this.engine.network.socket.request(MEDIASOUP_EVENTS.resumeConsumer, { consumerId });
|
|
300
|
-
}
|
|
301
|
-
|
|
302
165
|
private handleNewProducer(producerData: ProducerData): void {
|
|
303
166
|
if (this.producers.get(producerData.id)) {
|
|
304
167
|
return;
|
|
@@ -328,13 +191,13 @@ class Peer {
|
|
|
328
191
|
});
|
|
329
192
|
|
|
330
193
|
this.observer.on(MEDIASOUP_EVENTS.closeConsumer, (consumerId) => {
|
|
331
|
-
const consumer = this.
|
|
194
|
+
const consumer = this.getConsumerById(consumerId);
|
|
332
195
|
if (!consumer) {
|
|
333
196
|
return;
|
|
334
197
|
}
|
|
335
198
|
|
|
336
199
|
consumer.close();
|
|
337
|
-
this.
|
|
200
|
+
this.tracks.delete(consumer.appData.producerData.label);
|
|
338
201
|
this.observer.safeEmit(PEER_EVENTS.trackEnd, {
|
|
339
202
|
producerId: consumer.producerId,
|
|
340
203
|
kind: consumer.kind,
|
|
@@ -344,7 +207,7 @@ class Peer {
|
|
|
344
207
|
});
|
|
345
208
|
|
|
346
209
|
this.observer.on(MEDIASOUP_EVENTS.pauseConsumer, (consumerId) => {
|
|
347
|
-
const consumer = this.
|
|
210
|
+
const consumer = this.getConsumerById(consumerId);
|
|
348
211
|
if (!consumer) {
|
|
349
212
|
return;
|
|
350
213
|
}
|
|
@@ -354,7 +217,7 @@ class Peer {
|
|
|
354
217
|
});
|
|
355
218
|
|
|
356
219
|
this.observer.on(MEDIASOUP_EVENTS.resumeConsumer, (consumerId) => {
|
|
357
|
-
const consumer = this.
|
|
220
|
+
const consumer = this.getConsumerById(consumerId);
|
|
358
221
|
if (!consumer) {
|
|
359
222
|
return;
|
|
360
223
|
}
|
|
@@ -369,7 +232,7 @@ class Peer {
|
|
|
369
232
|
return;
|
|
370
233
|
}
|
|
371
234
|
|
|
372
|
-
const consumer = this.
|
|
235
|
+
const consumer = this.getConsumerById(payload.consumerId);
|
|
373
236
|
if (!consumer) {
|
|
374
237
|
return;
|
|
375
238
|
}
|
|
@@ -419,6 +282,12 @@ class Peer {
|
|
|
419
282
|
return;
|
|
420
283
|
}
|
|
421
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;
|
|
422
291
|
const { scores } = payload;
|
|
423
292
|
if (scores.length === 0) {
|
|
424
293
|
return;
|
|
@@ -426,13 +295,18 @@ class Peer {
|
|
|
426
295
|
|
|
427
296
|
let { score } = scores[0];
|
|
428
297
|
|
|
429
|
-
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
|
+
|
|
430
304
|
// video with multiple encodings has personal score for each encoding
|
|
431
305
|
const scoresSum = scores
|
|
432
306
|
.map(({ score: currentScore }) => currentScore)
|
|
433
307
|
.reduce((acc, curr) => acc + curr, 0);
|
|
434
308
|
|
|
435
|
-
score = Math.floor(scoresSum /
|
|
309
|
+
score = Math.floor(scoresSum / scores.length);
|
|
436
310
|
}
|
|
437
311
|
|
|
438
312
|
this.outgoingConnectionQuality = Peer.getConnectionQualityByScore(score);
|
|
@@ -440,21 +314,30 @@ class Peer {
|
|
|
440
314
|
});
|
|
441
315
|
|
|
442
316
|
this.observer.on(MEDIASOUP_EVENTS.consumerChangePreferredLayers, (payload: ChangePreferredLayersPayload) => {
|
|
443
|
-
const consumer = this.
|
|
317
|
+
const consumer = this.getConsumerById(payload.consumerId);
|
|
444
318
|
if (consumer?.isVideo) {
|
|
445
319
|
consumer.setCurrentTemporalLayer(payload.temporalLayer);
|
|
446
320
|
consumer.setCurrentSpatialLayer(payload.spatialLayer);
|
|
321
|
+
if (payload.spatialLayer === consumer.requestedSpatialLayer) {
|
|
322
|
+
consumer.setRequestedSpatialLayer(undefined);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
this.logger.debug('consumerChangePreferredLayers()', { consumer, ...payload });
|
|
447
326
|
}
|
|
448
327
|
});
|
|
449
328
|
}
|
|
450
329
|
|
|
451
330
|
private emitConnectionQuality(): void {
|
|
452
|
-
//
|
|
453
|
-
|
|
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
|
+
});
|
|
454
338
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
});
|
|
339
|
+
this.logger.debug('emitConnectionQuality()', { peer: this, connectionQuality });
|
|
340
|
+
}, 1000);
|
|
458
341
|
}
|
|
459
342
|
|
|
460
343
|
static getConnectionQualityByScore(score: number): ConnectionQuality {
|
|
@@ -469,13 +352,23 @@ class Peer {
|
|
|
469
352
|
return ConnectionQuality.GOOD;
|
|
470
353
|
}
|
|
471
354
|
|
|
472
|
-
|
|
473
|
-
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);
|
|
474
359
|
}
|
|
475
360
|
|
|
476
361
|
public getAllProducers(): PeerProducer[] {
|
|
477
362
|
return Array.from(this.producers.values());
|
|
478
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
|
+
}
|
|
479
372
|
}
|
|
480
373
|
|
|
481
374
|
export default Peer;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseScalabilityMode } from 'mediasoup-client';
|
|
2
|
-
import { MediaKind } from 'mediasoup-client/lib/RtpParameters';
|
|
2
|
+
import { MediaKind, RtpEncodingParameters } from 'mediasoup-client/lib/RtpParameters';
|
|
3
3
|
import { Consumer as MediasoupConsumer } from 'mediasoup-client/lib/types';
|
|
4
|
-
import { ConsumerData } from '../types/common';
|
|
4
|
+
import { ConsumerData, SpatialLayerParams } from '../types/common';
|
|
5
5
|
import Logger from './Logger';
|
|
6
6
|
|
|
7
7
|
class PeerConsumer {
|
|
@@ -17,6 +17,14 @@ class PeerConsumer {
|
|
|
17
17
|
|
|
18
18
|
public currentTemporalLayer = 0;
|
|
19
19
|
|
|
20
|
+
public requestedSpatialLayer?: number;
|
|
21
|
+
|
|
22
|
+
public currentMaxSpatialLayer: number;
|
|
23
|
+
|
|
24
|
+
public availableSpatialLayers: SpatialLayerParams[] = [];
|
|
25
|
+
|
|
26
|
+
public currentSpatialLayerParams?: SpatialLayerParams;
|
|
27
|
+
|
|
20
28
|
readonly id: string;
|
|
21
29
|
|
|
22
30
|
readonly kind: MediaKind;
|
|
@@ -36,6 +44,10 @@ class PeerConsumer {
|
|
|
36
44
|
this.appData = consumer.appData as ConsumerData;
|
|
37
45
|
this.consumer = consumer;
|
|
38
46
|
this.logger = new Logger('PeerConsumer');
|
|
47
|
+
this.currentMaxSpatialLayer = this.appData.producerData.maxSpatialLayer;
|
|
48
|
+
this.parseScalabilityMode();
|
|
49
|
+
this.setSpatialLayersParams();
|
|
50
|
+
this.setCurrentSpatialLayerParams();
|
|
39
51
|
}
|
|
40
52
|
|
|
41
53
|
get paused() {
|
|
@@ -76,23 +88,57 @@ class PeerConsumer {
|
|
|
76
88
|
}
|
|
77
89
|
}
|
|
78
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
|
+
|
|
79
116
|
setCurrentSpatialLayer(currentSpatialLayer: number): void {
|
|
80
|
-
if (this.
|
|
117
|
+
if (!this.isVideo) {
|
|
81
118
|
throw new Error('setCurrentSpatialLayer applies to video consumers only');
|
|
82
119
|
}
|
|
83
120
|
|
|
84
121
|
this.currentSpatialLayer = currentSpatialLayer;
|
|
85
122
|
this.logger.debug('setCurrentSpatialLayer()', { currentSpatialLayer });
|
|
123
|
+
this.setCurrentSpatialLayerParams();
|
|
86
124
|
}
|
|
87
125
|
|
|
88
126
|
setCurrentTemporalLayer(currentTemporalLayer: number): void {
|
|
89
|
-
if (this.
|
|
127
|
+
if (!this.isVideo) {
|
|
90
128
|
throw new Error('setCurrentTemporalLayer applies to video consumers only');
|
|
91
129
|
}
|
|
92
130
|
|
|
93
131
|
this.currentTemporalLayer = currentTemporalLayer;
|
|
94
132
|
this.logger.debug('setCurrentTemporalLayer()', { currentTemporalLayer });
|
|
95
133
|
}
|
|
134
|
+
|
|
135
|
+
setRequestedSpatialLayer(spatialLayer: number | undefined): void {
|
|
136
|
+
this.requestedSpatialLayer = spatialLayer;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
setCurrentMaxSpatialLayer(spatialLayer: number): void {
|
|
140
|
+
this.currentMaxSpatialLayer = spatialLayer;
|
|
141
|
+
}
|
|
96
142
|
}
|
|
97
143
|
|
|
98
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
|
|
|
@@ -2,11 +2,13 @@ import {
|
|
|
2
2
|
ChangePreferredLayersParams,
|
|
3
3
|
ConsumerScoreChangedPayload,
|
|
4
4
|
ProducerData,
|
|
5
|
-
|
|
5
|
+
ProducerRequestMaxSpatialLayer,
|
|
6
|
+
ProducerScoreChangedPayload, ProducerSetMaxSpatialLayer,
|
|
6
7
|
} from '../../types/common';
|
|
7
8
|
import Engine from '../index';
|
|
8
9
|
import { MEDIASOUP_EVENTS } from '../../constants/events';
|
|
9
10
|
import Logger from '../Logger';
|
|
11
|
+
import VideoTrack from '../media/tracks/VideoTrack';
|
|
10
12
|
|
|
11
13
|
class MediaSoupEventHandler {
|
|
12
14
|
private readonly engine: Engine;
|
|
@@ -108,6 +110,83 @@ class MediaSoupEventHandler {
|
|
|
108
110
|
|
|
109
111
|
peer.observer.safeEmit(MEDIASOUP_EVENTS.producerScoreChanged, payload);
|
|
110
112
|
});
|
|
113
|
+
|
|
114
|
+
connection.on(MEDIASOUP_EVENTS.producerRequestMaxSpatialLayer, async ({
|
|
115
|
+
producerId,
|
|
116
|
+
spatialLayer,
|
|
117
|
+
}: ProducerRequestMaxSpatialLayer) => {
|
|
118
|
+
const track = this.engine.media.getAllTracks().find((t) => t.getProducer()?.id === producerId);
|
|
119
|
+
if (!track) {
|
|
120
|
+
this.logger.warn('producerRequestMaxSpatialLayer()', { message: 'Producer not found' });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!(track instanceof VideoTrack)) {
|
|
125
|
+
this.logger.warn('producerRequestMaxSpatialLayer()', { message: 'Wrong producer kind' });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const currentMaxSpatialLayer = track.getMaxSpatialLayer();
|
|
130
|
+
if (currentMaxSpatialLayer === spatialLayer) {
|
|
131
|
+
this.logger.debug('producerRequestMaxSpatialLayer()', {
|
|
132
|
+
message: 'Skip set max spatial layer',
|
|
133
|
+
requestedSpatialLayer: spatialLayer,
|
|
134
|
+
currentMaxSpatialLayer,
|
|
135
|
+
});
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
await track.setMaxSpatialLayer(spatialLayer);
|
|
140
|
+
await this.engine.network.socket.request(MEDIASOUP_EVENTS.producerSetMaxSpatialLayer, {
|
|
141
|
+
producerId,
|
|
142
|
+
maxSpatialLayer: spatialLayer,
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
connection.on(MEDIASOUP_EVENTS.producerSetMaxSpatialLayer, async ({
|
|
147
|
+
peerId,
|
|
148
|
+
producerId,
|
|
149
|
+
spatialLayer,
|
|
150
|
+
}: ProducerSetMaxSpatialLayer) => {
|
|
151
|
+
const peer = this.engine.peers.find((item) => item.id === peerId);
|
|
152
|
+
if (!peer || peer.isMe) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const consumer = peer.getConsumerByProducerId(producerId);
|
|
157
|
+
if (!consumer) {
|
|
158
|
+
this.logger.warn('producerSetMaxSpatialLayer()', { message: 'Consumer not found' });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
consumer.setCurrentMaxSpatialLayer(spatialLayer);
|
|
163
|
+
this.logger.debug('producerSetMaxSpatialLayer()', {
|
|
164
|
+
producerId,
|
|
165
|
+
consumerId: consumer.id,
|
|
166
|
+
spatialLayer,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
if (consumer.requestedSpatialLayer !== spatialLayer) {
|
|
170
|
+
this.logger.debug('producerSetMaxSpatialLayer()', {
|
|
171
|
+
message: 'No need to request new preferred layer',
|
|
172
|
+
currentMaxSpatialLayer: spatialLayer,
|
|
173
|
+
requestedMaxSpatialLayer: consumer.requestedSpatialLayer,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await this.engine.network.socket.request(MEDIASOUP_EVENTS.consumerChangePreferredLayers, {
|
|
180
|
+
consumerId: consumer.id,
|
|
181
|
+
spatialLayer,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
this.logger.debug('requestVideoPreferredLayers()', {
|
|
185
|
+
producerId,
|
|
186
|
+
consumerId: consumer.id,
|
|
187
|
+
spatialLayer,
|
|
188
|
+
});
|
|
189
|
+
});
|
|
111
190
|
}
|
|
112
191
|
}
|
|
113
192
|
|