@livedigital/client 2.25.0-webinar-highload-test.1 → 2.26.0-stop-streams.2

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.
@@ -20,6 +20,7 @@ export declare type TrackProduceParams = {
20
20
  codecOptions?: ProducerCodecOptions;
21
21
  preferredCodec?: string;
22
22
  transformParams?: TrackTransformParams;
23
+ keyFrameRequestDelay?: number;
23
24
  };
24
25
  export declare type TrackTransformParams = {
25
26
  width?: number;
@@ -169,6 +170,10 @@ export declare type PayloadOfPublishedMedia = {
169
170
  kind: MediaKind;
170
171
  label: TrackLabel;
171
172
  };
173
+ export declare type SubscribeOptions = {
174
+ producerId: string;
175
+ paused?: boolean;
176
+ };
172
177
  export declare type PayloadOfUnpublishedMedia = PayloadOfPublishedMedia;
173
178
  export declare type ChangePreferredLayersPayload = PreferredLayersParams & {
174
179
  consumerId: string;
@@ -299,3 +304,10 @@ export declare type UpdatePeerAppDataPayload = {
299
304
  peerId: string;
300
305
  appData: Record<string, unknown>;
301
306
  };
307
+ export declare type CreateTracksPayload = {
308
+ mediaStreamTracks: MediaStreamTrack[];
309
+ constraints: MediaStreamConstraints;
310
+ };
311
+ export declare type TrackPublishParams = {
312
+ keyFrameRequestDelay?: number;
313
+ };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@livedigital/client",
3
3
  "author": "vlprojects",
4
4
  "license": "MIT",
5
- "version": "2.25.0-webinar-highload-test.1",
5
+ "version": "2.26.0-stop-streams.2",
6
6
  "private": false,
7
7
  "bugs": {
8
8
  "url": "https://github.com/vlprojects/livedigital-sdk/issues"
@@ -9,6 +9,7 @@ import {
9
9
  Role,
10
10
  ProducerSetMaxSpatialLayer,
11
11
  PeerInfo,
12
+ SubscribeOptions,
12
13
  } from '../types/common';
13
14
  import EnhancedEventEmitter from '../EnhancedEventEmitter';
14
15
  import Engine from './index';
@@ -118,7 +119,7 @@ class Peer {
118
119
  }));
119
120
  }
120
121
 
121
- public async subscribe(producerId: string): Promise<void> {
122
+ public async subscribe({ producerId, paused = false }: SubscribeOptions): Promise<void> {
122
123
  try {
123
124
  const producer = this.producers.get(producerId);
124
125
  if (!producer) {
@@ -132,7 +133,7 @@ class Peer {
132
133
  return;
133
134
  }
134
135
 
135
- const { consumer } = await this.engine.network.createConsumer({
136
+ const { consumer, isProducerPaused } = await this.engine.network.createConsumer({
136
137
  producerId: producer.id,
137
138
  rtpCapabilities: this.engine.media.mediasoupDevice.rtpCapabilities,
138
139
  producerPeerId: this.id,
@@ -147,9 +148,13 @@ class Peer {
147
148
  engine: this.engine,
148
149
  isPaused: true,
149
150
  peerEventEmitter: this.observer,
151
+ isProducerPaused,
150
152
  });
151
153
 
152
- await track.resume();
154
+ if (!paused) {
155
+ await track.resume();
156
+ }
157
+
153
158
  this.tracks.set(track.label, track);
154
159
  this.logger.debug(`Subscribed for ${producer.kind}`, { peer: this });
155
160
  } catch (error) {
@@ -222,6 +227,7 @@ class Peer {
222
227
  return;
223
228
  }
224
229
 
230
+ track.setIsTrackProducerPaused(true);
225
231
  await track.pause();
226
232
  });
227
233
 
@@ -231,6 +237,7 @@ class Peer {
231
237
  return;
232
238
  }
233
239
 
240
+ track.setIsTrackProducerPaused(false);
234
241
  await track.resume();
235
242
  });
236
243
 
@@ -223,6 +223,12 @@ class Engine {
223
223
  }
224
224
 
225
225
  public removePeer(peerId: string): void {
226
+ const peer = this.peersRepository.get(peerId);
227
+ if (!peer) {
228
+ return;
229
+ }
230
+
231
+ peer.tracks.forEach((track) => track.close());
226
232
  this.peersRepository.delete(peerId);
227
233
  }
228
234
 
@@ -451,20 +457,7 @@ class Engine {
451
457
 
452
458
  private watchClientEvents(): void {
453
459
  this.clientEventEmitter.on(INTERNAL_CLIENT_EVENTS.trackProduced, (track: Track) => {
454
- const myPeer = this.peersRepository.get(<string> this.mySocketId);
455
- if (!myPeer) {
456
- return;
457
- }
458
-
459
- const peerTrack = new PeerTrack({
460
- mediaStreamTrack: track.mediaStreamTrack,
461
- label: track.getLabel(),
462
- engine: this,
463
- isPaused: track.isPaused,
464
- peerEventEmitter: myPeer.observer,
465
- });
466
-
467
- myPeer.tracks.set(peerTrack.label, peerTrack);
460
+ this.createSelfPeerTrack(track);
468
461
  });
469
462
 
470
463
  this.clientEventEmitter.on(INTERNAL_CLIENT_EVENTS.trackUnproduced, (track: Track) => {
@@ -475,14 +468,30 @@ class Engine {
475
468
 
476
469
  this.clientEventEmitter.on(INTERNAL_CLIENT_EVENTS.trackPaused, (track: Track) => {
477
470
  const peerTrack = this.myPeer?.tracks.get(track.getLabel());
478
- peerTrack?.pause();
471
+ peerTrack?.close();
479
472
  });
480
473
 
481
474
  this.clientEventEmitter.on(INTERNAL_CLIENT_EVENTS.trackResumed, (track: Track) => {
482
- const peerTrack = this.myPeer?.tracks.get(track.getLabel());
483
- peerTrack?.resume();
475
+ this.createSelfPeerTrack(track);
484
476
  });
485
477
  }
478
+
479
+ private createSelfPeerTrack(track: Track): void {
480
+ const myPeer = this.peersRepository.get(<string> this.mySocketId);
481
+ if (!myPeer) {
482
+ return;
483
+ }
484
+
485
+ const peerTrack = new PeerTrack({
486
+ mediaStreamTrack: track.mediaStreamTrack,
487
+ label: track.getLabel(),
488
+ engine: this,
489
+ isPaused: track.isPaused,
490
+ peerEventEmitter: myPeer.observer,
491
+ });
492
+
493
+ myPeer.tracks.set(peerTrack.label, peerTrack);
494
+ }
486
495
  }
487
496
 
488
497
  export default Engine;
@@ -3,7 +3,11 @@ import { RtpCapabilities, RtpCodecCapability } from 'mediasoup-client/lib/RtpPar
3
3
  import VideoTrack from './tracks/VideoTrack';
4
4
  import AudioTrack from './tracks/AudioTrack';
5
5
  import {
6
- CreateScreenVideoTrackOptions, CreateVideoTrackParams, LogLevel, Track,
6
+ CreateScreenVideoTrackOptions,
7
+ CreateTracksPayload,
8
+ CreateVideoTrackParams,
9
+ LogLevel,
10
+ Track,
7
11
  } from '../../types/common';
8
12
  import Logger from '../Logger';
9
13
  import { VIDEO_CONSTRAINS } from '../../constants/videoConstrains';
@@ -11,6 +15,7 @@ import { SCREEN_SHARING_SIMULCAST_ENCODINGS, WEBCAM_SIMULCAST_ENCODINGS } from '
11
15
  import { CreateMediaParams } from '../../types/engine';
12
16
  import Engine from '../index';
13
17
  import EnhancedEventEmitter from '../../EnhancedEventEmitter';
18
+ import MediaStreamTrackManager from './tracks/MediaStreamTrackManager';
14
19
 
15
20
  class Media {
16
21
  public isDeviceLoaded = false;
@@ -27,6 +32,8 @@ class Media {
27
32
 
28
33
  readonly #clientEventEmitter: EnhancedEventEmitter;
29
34
 
35
+ readonly #mediaStreamTrackManager: MediaStreamTrackManager;
36
+
30
37
  constructor(params: CreateMediaParams) {
31
38
  this.#logLevel = params.logLevel;
32
39
  this.#logger = new Logger({
@@ -35,6 +42,7 @@ class Media {
35
42
  });
36
43
  this.#engine = params.engine;
37
44
  this.#clientEventEmitter = params.clientEventEmitter;
45
+ this.#mediaStreamTrackManager = new MediaStreamTrackManager(params.logLevel);
38
46
  }
39
47
 
40
48
  get mediasoupDevice(): Device {
@@ -62,20 +70,20 @@ class Media {
62
70
  return codecs.find((c) => c.mimeType.toLowerCase() === `${track.kind}/${track.getPreferredCodec()}`);
63
71
  }
64
72
 
65
- private createTracks(stream: MediaStream): Track[] {
66
- const mediaStreamTracks = stream.getTracks();
73
+ private createTracks({ constraints, mediaStreamTracks }: CreateTracksPayload): Track[] {
67
74
  return mediaStreamTracks.map((mediaStreamTrack) => {
68
75
  const params = {
69
76
  mediaStreamTrack,
70
77
  logLevel: this.#logLevel,
71
78
  engine: this.#engine,
72
79
  clientEventEmitter: this.#clientEventEmitter,
80
+ constraints,
81
+ mediaStreamTrackManager: this.#mediaStreamTrackManager,
73
82
  };
74
83
 
75
84
  const track = mediaStreamTrack.kind === 'audio' ? new AudioTrack(params) : new VideoTrack(params);
76
85
  this.tracks.set(track.id, track);
77
86
  this.#logger.debug('createTrack() track created', {
78
- streamId: stream.id,
79
87
  trackId: track.id,
80
88
  kind: track.kind,
81
89
  });
@@ -85,14 +93,13 @@ class Media {
85
93
  }
86
94
 
87
95
  async createUserMediaTracks(constraints: MediaStreamConstraints): Promise<Track[]> {
88
- const stream = await navigator.mediaDevices.getUserMedia(constraints);
89
- this.#logger.debug('createUserMediaTrack() stream created', { streamId: stream.id });
90
- return this.createTracks(stream);
96
+ const mediaStreamTracks = await this.#mediaStreamTrackManager.createUserMediaTracks(constraints);
97
+ return this.createTracks({ constraints, mediaStreamTracks });
91
98
  }
92
99
 
93
100
  async createDisplayMediaTracks(constraints: MediaStreamConstraints): Promise<Track[]> {
94
- const stream = await navigator.mediaDevices.getDisplayMedia(constraints);
95
- return this.createTracks(stream);
101
+ const mediaStreamTracks = await this.#mediaStreamTrackManager.createDisplayMediaTracks(constraints);
102
+ return this.createTracks({ constraints, mediaStreamTracks });
96
103
  }
97
104
 
98
105
  deleteTrack(track: Track) {
@@ -5,27 +5,31 @@ import {
5
5
  EncoderConfig,
6
6
  LogLevel,
7
7
  SocketResponse,
8
- TrackLabel, TrackOutboundStats,
8
+ TrackLabel,
9
+ TrackOutboundStats,
9
10
  TrackProduceParams,
10
11
  } from '../../../types/common';
11
12
  import Logger from '../../Logger';
12
13
  import Engine from '../../index';
13
14
  import PeerConsumer from '../../PeerConsumer';
14
15
  import { PRODUCER_CHECK_STATE_TIMEOUT } from '../../../constants/common';
15
- import Timeout = NodeJS.Timeout;
16
- import { MEDIASOUP_EVENTS, INTERNAL_CLIENT_EVENTS, CLIENT_EVENTS } from '../../../constants/events';
16
+ import { CLIENT_EVENTS, INTERNAL_CLIENT_EVENTS, MEDIASOUP_EVENTS } from '../../../constants/events';
17
17
  import EnhancedEventEmitter from '../../../EnhancedEventEmitter';
18
18
  import filterStatsCodecs from '../../../helpers/filterStatsCodecs';
19
+ import Timeout = NodeJS.Timeout;
20
+ import MediaStreamTrackManager from './MediaStreamTrackManager';
19
21
 
20
22
  export type BaseTrackConstructorParams = {
21
23
  mediaStreamTrack: MediaStreamTrack,
22
24
  logLevel: LogLevel,
23
25
  engine: Engine,
24
26
  clientEventEmitter: EnhancedEventEmitter,
27
+ constraints: MediaStreamConstraints;
28
+ mediaStreamTrackManager: MediaStreamTrackManager;
25
29
  };
26
30
 
27
31
  class BaseTrack {
28
- readonly #mediaStreamTrack: MediaStreamTrack;
32
+ #mediaStreamTrack: MediaStreamTrack;
29
33
 
30
34
  protected encoderConfig: EncoderConfig = {};
31
35
 
@@ -47,13 +51,19 @@ class BaseTrack {
47
51
 
48
52
  #closed = false;
49
53
 
54
+ readonly #constraints: MediaStreamConstraints;
55
+
56
+ readonly #mediaStreamTrackManager: MediaStreamTrackManager;
57
+
50
58
  constructor(params: BaseTrackConstructorParams) {
51
59
  const {
52
- mediaStreamTrack, logLevel, engine, clientEventEmitter,
60
+ mediaStreamTrack, logLevel, engine, clientEventEmitter, constraints, mediaStreamTrackManager,
53
61
  } = params;
54
62
  this.#mediaStreamTrack = mediaStreamTrack;
55
63
  this.#engine = engine;
56
64
  this.#clientEventEmitter = clientEventEmitter;
65
+ this.#constraints = constraints;
66
+ this.#mediaStreamTrackManager = mediaStreamTrackManager;
57
67
  this.logger = new Logger({
58
68
  namespace: 'Track',
59
69
  logLevel,
@@ -212,6 +222,7 @@ class BaseTrack {
212
222
  codecOptions,
213
223
  trackTransformParams,
214
224
  preferredCodec,
225
+ keyFrameRequestDelay,
215
226
  },
216
227
  } = this.producer;
217
228
 
@@ -222,6 +233,7 @@ class BaseTrack {
222
233
  codecOptions,
223
234
  transformParams: trackTransformParams,
224
235
  preferredCodec,
236
+ keyFrameRequestDelay,
225
237
  });
226
238
 
227
239
  this.logger.debug('restartProducer()', { track: this });
@@ -257,6 +269,7 @@ class BaseTrack {
257
269
  }
258
270
 
259
271
  private async cancelProducerCheckState(): Promise<void> {
272
+ this.logger.debug('cancelProducerCheckState()', { track: this });
260
273
  if (this.#checkStateTimeout) {
261
274
  clearTimeout(this.#checkStateTimeout);
262
275
  }
@@ -265,7 +278,11 @@ class BaseTrack {
265
278
  }
266
279
 
267
280
  async produce({
268
- encodings, codecOptions, preferredCodec, transformParams,
281
+ encodings,
282
+ codecOptions,
283
+ preferredCodec,
284
+ transformParams,
285
+ keyFrameRequestDelay,
269
286
  }: TrackProduceParams): Promise<void> {
270
287
  if (!this.#engine.cahPublish) {
271
288
  this.logger.error('produce()', { message: 'Not enough access to produce' });
@@ -285,6 +302,7 @@ class BaseTrack {
285
302
  codecOptions,
286
303
  preferredCodec,
287
304
  codec,
305
+ keyFrameRequestDelay,
288
306
  };
289
307
 
290
308
  const producer = await this.#engine.network.sendTransport?.produce({
@@ -355,8 +373,10 @@ class BaseTrack {
355
373
  }
356
374
 
357
375
  try {
376
+ await this.cancelProducerCheckState();
358
377
  await this.pauseRemoteProducer(this.producer.id);
359
378
  this.producer.pause();
379
+ this.#mediaStreamTrack.stop();
360
380
  this.clientEventEmitter.emit(INTERNAL_CLIENT_EVENTS.trackPaused, this);
361
381
  this.logger.debug('pause()', { track: this });
362
382
  } catch (error) {
@@ -372,10 +392,16 @@ class BaseTrack {
372
392
  }
373
393
 
374
394
  try {
395
+ const track = await this.#mediaStreamTrackManager.createMediaStreamTrack({
396
+ label: this.label,
397
+ constraints: this.#constraints,
398
+ });
399
+ this.#mediaStreamTrack = track;
400
+ await this.producer.replaceTrack({ track });
375
401
  await this.resumeRemoteProducer(this.producer.id);
376
402
  this.producer.resume();
377
403
  this.clientEventEmitter.emit(INTERNAL_CLIENT_EVENTS.trackResumed, this);
378
- this.logger.error('resume()', { track: this });
404
+ this.logger.debug('resume()', { track: this });
379
405
  } catch (error) {
380
406
  this.logger.error('resume()', { error, track: this });
381
407
  throw new Error('Can`t resume track');
@@ -0,0 +1,46 @@
1
+ import { LogLevel, TrackLabel } from '../../../types/common';
2
+ import Logger from '../../Logger';
3
+
4
+ export type GetMediaStreamTrackParams = {
5
+ label: TrackLabel;
6
+ constraints: MediaStreamConstraints;
7
+ };
8
+
9
+ class MediaStreamTrackManager {
10
+ readonly #logger: Logger;
11
+
12
+ constructor(logLevel: LogLevel) {
13
+ this.#logger = new Logger({
14
+ namespace: 'MediaStreamTrackManager',
15
+ logLevel,
16
+ });
17
+ }
18
+
19
+ async createMediaStreamTrack({ label, constraints }: GetMediaStreamTrackParams): Promise<MediaStreamTrack> {
20
+ if (MediaStreamTrackManager.isDisplayMedia(label)) {
21
+ const [track] = await this.createDisplayMediaTracks(constraints);
22
+ return track;
23
+ }
24
+
25
+ const [track] = await this.createUserMediaTracks(constraints);
26
+ return track;
27
+ }
28
+
29
+ private static isDisplayMedia(label: TrackLabel): boolean {
30
+ return [TrackLabel.ScreenVideo, TrackLabel.ScreenAudio].includes(label);
31
+ }
32
+
33
+ async createUserMediaTracks(constraints: MediaStreamConstraints): Promise<MediaStreamTrack[]> {
34
+ const stream = await navigator.mediaDevices.getUserMedia(constraints);
35
+ this.#logger.debug('createUserMediaTracks() stream created', { streamId: stream.id });
36
+ return stream.getTracks();
37
+ }
38
+
39
+ async createDisplayMediaTracks(constraints: MediaStreamConstraints): Promise<MediaStreamTrack[]> {
40
+ const stream = await navigator.mediaDevices.getDisplayMedia(constraints);
41
+ this.#logger.debug('getDisplayMediaTracks() stream created', { streamId: stream.id });
42
+ return stream.getTracks();
43
+ }
44
+ }
45
+
46
+ export default MediaStreamTrackManager;
@@ -20,6 +20,7 @@ interface PeerTrackConstructor {
20
20
  consumer?: PeerConsumer,
21
21
  engine: Engine,
22
22
  isPaused: boolean,
23
+ isProducerPaused?: boolean,
23
24
  peerEventEmitter: EnhancedEventEmitter,
24
25
  }
25
26
 
@@ -44,6 +45,8 @@ class PeerTrack {
44
45
 
45
46
  #closed = false;
46
47
 
48
+ #isTrackProducerPaused?: boolean;
49
+
47
50
  constructor(payload: PeerTrackConstructor) {
48
51
  this.#mediaStreamTrack = payload.mediaStreamTrack;
49
52
  this.label = payload.label;
@@ -55,6 +58,7 @@ class PeerTrack {
55
58
  });
56
59
  this.#peerEventEmitter = payload.peerEventEmitter;
57
60
  this.#paused = payload.isPaused;
61
+ this.#isTrackProducerPaused = payload.isProducerPaused;
58
62
  this.#peerEventEmitter.safeEmit(PEER_EVENTS.trackStart, this);
59
63
  }
60
64
 
@@ -131,11 +135,17 @@ class PeerTrack {
131
135
  return;
132
136
  }
133
137
 
138
+ if (this.#isTrackProducerPaused) {
139
+ this.#logger.debug('resume()', { message: 'Can not resume track, producer is paused', track: this, peer: this });
140
+ return;
141
+ }
142
+
134
143
  try {
135
144
  await this.#engine.network.resumeRemoteConsumer(this.consumer.id);
136
145
  this.consumer.resume();
137
146
  this.#paused = false;
138
147
  this.#peerEventEmitter.safeEmit(PEER_EVENTS.trackResumed, this);
148
+ this.checkConsumerState();
139
149
  this.#logger.debug('resume()', { track: this, peer: this });
140
150
  } catch (error) {
141
151
  this.#logger.warn('resume()', { error });
@@ -427,6 +437,10 @@ class PeerTrack {
427
437
 
428
438
  clearTimeout(this.#checkStateTimeout);
429
439
  }
440
+
441
+ setIsTrackProducerPaused(value: boolean): void {
442
+ this.#isTrackProducerPaused = value;
443
+ }
430
444
  }
431
445
 
432
446
  export default PeerTrack;
@@ -1,7 +1,9 @@
1
1
  import { ProducerCodecOptions } from 'mediasoup-client/lib/Producer';
2
2
  import { RtpEncodingParameters } from 'mediasoup-client/lib/RtpParameters';
3
3
  import { WEBCAM_SIMULCAST_ENCODINGS } from '../../../constants/simulcastEncodings';
4
- import { TrackLabel, VideoCodec, VideoEncoderConfig } from '../../../types/common';
4
+ import {
5
+ TrackLabel, TrackPublishParams, VideoCodec, VideoEncoderConfig,
6
+ } from '../../../types/common';
5
7
  import BaseTrack from './BaseTrack';
6
8
  import TrackWithCodecOptions from './TrackWithCodecOptions';
7
9
  import TrackWithEncodings from './TrackWithEncodings';
@@ -65,12 +67,13 @@ class VideoTrack extends BaseTrack implements TrackWithCodecOptions, TrackWithEn
65
67
  return producer.maxSpatialLayer;
66
68
  }
67
69
 
68
- async publish(): Promise<void> {
70
+ async publish(params: TrackPublishParams = {}): Promise<void> {
69
71
  await this.produce({
70
72
  encodings: this.getEncodings(),
71
73
  preferredCodec: this.getPreferredCodec(),
72
74
  transformParams: this.transformParams,
73
75
  codecOptions: this.getCodecOptions(),
76
+ keyFrameRequestDelay: params.keyFrameRequestDelay ?? 0,
74
77
  });
75
78
 
76
79
  this.clientEventEmitter.emit(INTERNAL_CLIENT_EVENTS.trackProduced, this);
@@ -27,6 +27,7 @@ export type TrackProduceParams = {
27
27
  codecOptions?: ProducerCodecOptions,
28
28
  preferredCodec?: string,
29
29
  transformParams?: TrackTransformParams,
30
+ keyFrameRequestDelay?: number,
30
31
  };
31
32
 
32
33
  export type TrackTransformParams = {
@@ -205,6 +206,11 @@ export type PayloadOfPublishedMedia = {
205
206
  label: TrackLabel,
206
207
  };
207
208
 
209
+ export type SubscribeOptions = {
210
+ producerId: string,
211
+ paused?: boolean,
212
+ };
213
+
208
214
  export type PayloadOfUnpublishedMedia = PayloadOfPublishedMedia;
209
215
 
210
216
  export type ChangePreferredLayersPayload = PreferredLayersParams & {
@@ -343,3 +349,12 @@ export type UpdatePeerAppDataPayload = {
343
349
  peerId: string,
344
350
  appData: Record<string, unknown>,
345
351
  };
352
+
353
+ export type CreateTracksPayload = {
354
+ mediaStreamTracks: MediaStreamTrack[],
355
+ constraints: MediaStreamConstraints,
356
+ };
357
+
358
+ export type TrackPublishParams = {
359
+ keyFrameRequestDelay?: number;
360
+ };