@stream-io/video-client 1.16.4 → 1.16.5

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.
@@ -10,6 +10,7 @@ export type PublisherConstructorOpts = BasePeerConnectionOpts & {
10
10
  */
11
11
  export declare class Publisher extends BasePeerConnection {
12
12
  private readonly transceiverCache;
13
+ private readonly clonedTracks;
13
14
  private publishOptions;
14
15
  /**
15
16
  * Constructs a new `Publisher` instance.
@@ -92,4 +93,6 @@ export declare class Publisher extends BasePeerConnection {
92
93
  * Converts the given transceiver to a `TrackInfo` object.
93
94
  */
94
95
  private toTrackInfo;
96
+ private cloneTrack;
97
+ private stopTrack;
95
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-client",
3
- "version": "1.16.4",
3
+ "version": "1.16.5",
4
4
  "packageManager": "yarn@3.2.4",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.es.js",
@@ -31,6 +31,7 @@ export type PublisherConstructorOpts = BasePeerConnectionOpts & {
31
31
  */
32
32
  export class Publisher extends BasePeerConnection {
33
33
  private readonly transceiverCache = new TransceiverCache();
34
+ private readonly clonedTracks = new Set<MediaStreamTrack>();
34
35
  private publishOptions: PublishOption[];
35
36
 
36
37
  /**
@@ -79,6 +80,7 @@ export class Publisher extends BasePeerConnection {
79
80
  dispose() {
80
81
  super.dispose();
81
82
  this.stopAllTracks();
83
+ this.clonedTracks.clear();
82
84
  }
83
85
 
84
86
  /**
@@ -100,7 +102,7 @@ export class Publisher extends BasePeerConnection {
100
102
 
101
103
  // create a clone of the track as otherwise the same trackId will
102
104
  // appear in the SDP in multiple transceivers
103
- const trackToPublish = track.clone();
105
+ const trackToPublish = this.cloneTrack(track);
104
106
 
105
107
  const transceiver = this.transceiverCache.get(publishOption);
106
108
  if (!transceiver) {
@@ -108,7 +110,7 @@ export class Publisher extends BasePeerConnection {
108
110
  } else {
109
111
  const previousTrack = transceiver.sender.track;
110
112
  await transceiver.sender.replaceTrack(trackToPublish);
111
- previousTrack?.stop();
113
+ this.stopTrack(previousTrack);
112
114
  }
113
115
  }
114
116
  };
@@ -153,7 +155,7 @@ export class Publisher extends BasePeerConnection {
153
155
 
154
156
  // take the track from the existing transceiver for the same track type,
155
157
  // clone it and publish it with the new publish options
156
- const track = item.transceiver.sender.track!.clone();
158
+ const track = this.cloneTrack(item.transceiver.sender.track!);
157
159
  this.addTransceiver(track, publishOption);
158
160
  }
159
161
 
@@ -167,7 +169,7 @@ export class Publisher extends BasePeerConnection {
167
169
  );
168
170
  if (hasPublishOption) continue;
169
171
  // it is safe to stop the track here, it is a clone
170
- transceiver.sender.track?.stop();
172
+ this.stopTrack(transceiver.sender.track);
171
173
  await transceiver.sender.replaceTrack(null);
172
174
  }
173
175
  };
@@ -209,7 +211,7 @@ export class Publisher extends BasePeerConnection {
209
211
  for (const item of this.transceiverCache.items()) {
210
212
  const { publishOption, transceiver } = item;
211
213
  if (!trackTypes.includes(publishOption.trackType)) continue;
212
- transceiver.sender.track?.stop();
214
+ this.stopTrack(transceiver.sender.track);
213
215
  }
214
216
  };
215
217
 
@@ -218,7 +220,10 @@ export class Publisher extends BasePeerConnection {
218
220
  */
219
221
  stopAllTracks = () => {
220
222
  for (const { transceiver } of this.transceiverCache.items()) {
221
- transceiver.sender.track?.stop();
223
+ this.stopTrack(transceiver.sender.track);
224
+ }
225
+ for (const track of this.clonedTracks) {
226
+ this.stopTrack(track);
222
227
  }
223
228
  };
224
229
 
@@ -433,4 +438,16 @@ export class Publisher extends BasePeerConnection {
433
438
  publishOptionId: publishOption.id,
434
439
  };
435
440
  };
441
+
442
+ private cloneTrack = (track: MediaStreamTrack): MediaStreamTrack => {
443
+ const clone = track.clone();
444
+ this.clonedTracks.add(clone);
445
+ return clone;
446
+ };
447
+
448
+ private stopTrack = (track: MediaStreamTrack | null | undefined) => {
449
+ if (!track) return;
450
+ track.stop();
451
+ this.clonedTracks.delete(track);
452
+ };
436
453
  }
@@ -116,6 +116,7 @@ describe('Publisher', () => {
116
116
  },
117
117
  ],
118
118
  });
119
+ expect(publisher['clonedTracks'].size).toBe(1);
119
120
  });
120
121
 
121
122
  it('should update an existing transceiver for a new track', async () => {
@@ -597,10 +598,22 @@ describe('Publisher', () => {
597
598
  );
598
599
  vi.spyOn(inactiveTrack, 'readyState', 'get').mockReturnValue('ended');
599
600
 
601
+ const audioTransceiver = new RTCRtpTransceiver();
602
+ const audioTrack = new MediaStreamTrack();
603
+ vi.spyOn(audioTrack, 'kind', 'get').mockReturnValue('audio');
604
+ vi.spyOn(audioTrack, 'enabled', 'get').mockReturnValue(true);
605
+ vi.spyOn(audioTransceiver.sender, 'track', 'get').mockReturnValue(
606
+ audioTrack,
607
+ );
608
+
600
609
  // @ts-expect-error incomplete data
601
610
  cache.add({ trackType: TrackType.VIDEO, id: 1 }, transceiver);
602
611
  // @ts-expect-error incomplete data
603
612
  cache.add({ trackType: TrackType.VIDEO, id: 2 }, inactiveTransceiver);
613
+ // @ts-expect-error incomplete data
614
+ cache.add({ trackType: TrackType.AUDIO, id: 3 }, audioTransceiver);
615
+
616
+ publisher['clonedTracks'].add(track).add(inactiveTrack).add(audioTrack);
604
617
  });
605
618
 
606
619
  it('negotiate should set up the local and remote descriptions', async () => {
@@ -666,13 +679,13 @@ describe('Publisher', () => {
666
679
 
667
680
  it('getPublishedTracks returns the published tracks', () => {
668
681
  const tracks = publisher.getPublishedTracks();
669
- expect(tracks).toHaveLength(1);
682
+ expect(tracks).toHaveLength(2);
670
683
  expect(tracks[0].readyState).toBe('live');
671
684
  });
672
685
 
673
686
  it('getAnnouncedTracks should return all tracks', () => {
674
687
  const trackInfos = publisher.getAnnouncedTracks('');
675
- expect(trackInfos).toHaveLength(2);
688
+ expect(trackInfos).toHaveLength(3);
676
689
  expect(trackInfos[0].muted).toBe(false);
677
690
  expect(trackInfos[0].mid).toBe('0');
678
691
  expect(trackInfos[1].muted).toBe(true);
@@ -701,15 +714,19 @@ describe('Publisher', () => {
701
714
  it('stopTracks should stop tracks', () => {
702
715
  const track = cache['cache'][0].transceiver.sender.track;
703
716
  vi.spyOn(track, 'stop');
717
+ expect(publisher['clonedTracks'].size).toBe(3);
704
718
  publisher.stopTracks(TrackType.VIDEO);
705
719
  expect(track!.stop).toHaveBeenCalled();
720
+ expect(publisher['clonedTracks'].size).toBe(1);
706
721
  });
707
722
 
708
723
  it('stopAllTracks should stop all tracks', () => {
709
724
  const track = cache['cache'][0].transceiver.sender.track;
710
725
  vi.spyOn(track, 'stop');
726
+ expect(publisher['clonedTracks'].size).toBe(3);
711
727
  publisher.stopAllTracks();
712
728
  expect(track!.stop).toHaveBeenCalled();
729
+ expect(publisher['clonedTracks'].size).toBe(0);
713
730
  });
714
731
  });
715
732
  });