Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions packages/client/src/rtc/Publisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export type PublisherConstructorOpts = BasePeerConnectionOpts & {
*/
export class Publisher extends BasePeerConnection {
private readonly transceiverCache = new TransceiverCache();
private readonly clonedTracks = new Set<MediaStreamTrack>();
private publishOptions: PublishOption[];

/**
Expand Down Expand Up @@ -79,6 +80,7 @@ export class Publisher extends BasePeerConnection {
dispose() {
super.dispose();
this.stopAllTracks();
this.clonedTracks.clear();
}

/**
Expand All @@ -100,15 +102,15 @@ export class Publisher extends BasePeerConnection {

// create a clone of the track as otherwise the same trackId will
// appear in the SDP in multiple transceivers
const trackToPublish = track.clone();
const trackToPublish = this.cloneTrack(track);

const transceiver = this.transceiverCache.get(publishOption);
if (!transceiver) {
this.addTransceiver(trackToPublish, publishOption);
} else {
const previousTrack = transceiver.sender.track;
await transceiver.sender.replaceTrack(trackToPublish);
previousTrack?.stop();
this.stopTrack(previousTrack);
}
}
};
Expand Down Expand Up @@ -153,7 +155,7 @@ export class Publisher extends BasePeerConnection {

// take the track from the existing transceiver for the same track type,
// clone it and publish it with the new publish options
const track = item.transceiver.sender.track!.clone();
const track = this.cloneTrack(item.transceiver.sender.track!);
this.addTransceiver(track, publishOption);
}

Expand All @@ -167,7 +169,7 @@ export class Publisher extends BasePeerConnection {
);
if (hasPublishOption) continue;
// it is safe to stop the track here, it is a clone
transceiver.sender.track?.stop();
this.stopTrack(transceiver.sender.track);
await transceiver.sender.replaceTrack(null);
}
};
Expand Down Expand Up @@ -209,7 +211,7 @@ export class Publisher extends BasePeerConnection {
for (const item of this.transceiverCache.items()) {
const { publishOption, transceiver } = item;
if (!trackTypes.includes(publishOption.trackType)) continue;
transceiver.sender.track?.stop();
this.stopTrack(transceiver.sender.track);
}
};

Expand All @@ -218,7 +220,10 @@ export class Publisher extends BasePeerConnection {
*/
stopAllTracks = () => {
for (const { transceiver } of this.transceiverCache.items()) {
transceiver.sender.track?.stop();
this.stopTrack(transceiver.sender.track);
}
for (const track of this.clonedTracks) {
this.stopTrack(track);
}
};

Expand Down Expand Up @@ -433,4 +438,16 @@ export class Publisher extends BasePeerConnection {
publishOptionId: publishOption.id,
};
};

private cloneTrack = (track: MediaStreamTrack): MediaStreamTrack => {
const clone = track.clone();
this.clonedTracks.add(clone);
return clone;
};

private stopTrack = (track: MediaStreamTrack | null | undefined) => {
if (!track) return;
track.stop();
this.clonedTracks.delete(track);
};
}
21 changes: 19 additions & 2 deletions packages/client/src/rtc/__tests__/Publisher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ describe('Publisher', () => {
},
],
});
expect(publisher['clonedTracks'].size).toBe(1);
});

it('should update an existing transceiver for a new track', async () => {
Expand Down Expand Up @@ -597,10 +598,22 @@ describe('Publisher', () => {
);
vi.spyOn(inactiveTrack, 'readyState', 'get').mockReturnValue('ended');

const audioTransceiver = new RTCRtpTransceiver();
const audioTrack = new MediaStreamTrack();
vi.spyOn(audioTrack, 'kind', 'get').mockReturnValue('audio');
vi.spyOn(audioTrack, 'enabled', 'get').mockReturnValue(true);
vi.spyOn(audioTransceiver.sender, 'track', 'get').mockReturnValue(
audioTrack,
);

// @ts-expect-error incomplete data
cache.add({ trackType: TrackType.VIDEO, id: 1 }, transceiver);
// @ts-expect-error incomplete data
cache.add({ trackType: TrackType.VIDEO, id: 2 }, inactiveTransceiver);
// @ts-expect-error incomplete data
cache.add({ trackType: TrackType.AUDIO, id: 3 }, audioTransceiver);

publisher['clonedTracks'].add(track).add(inactiveTrack).add(audioTrack);
});

it('negotiate should set up the local and remote descriptions', async () => {
Expand Down Expand Up @@ -666,13 +679,13 @@ describe('Publisher', () => {

it('getPublishedTracks returns the published tracks', () => {
const tracks = publisher.getPublishedTracks();
expect(tracks).toHaveLength(1);
expect(tracks).toHaveLength(2);
expect(tracks[0].readyState).toBe('live');
});

it('getAnnouncedTracks should return all tracks', () => {
const trackInfos = publisher.getAnnouncedTracks('');
expect(trackInfos).toHaveLength(2);
expect(trackInfos).toHaveLength(3);
expect(trackInfos[0].muted).toBe(false);
expect(trackInfos[0].mid).toBe('0');
expect(trackInfos[1].muted).toBe(true);
Expand Down Expand Up @@ -701,15 +714,19 @@ describe('Publisher', () => {
it('stopTracks should stop tracks', () => {
const track = cache['cache'][0].transceiver.sender.track;
vi.spyOn(track, 'stop');
expect(publisher['clonedTracks'].size).toBe(3);
publisher.stopTracks(TrackType.VIDEO);
expect(track!.stop).toHaveBeenCalled();
expect(publisher['clonedTracks'].size).toBe(1);
});

it('stopAllTracks should stop all tracks', () => {
const track = cache['cache'][0].transceiver.sender.track;
vi.spyOn(track, 'stop');
expect(publisher['clonedTracks'].size).toBe(3);
publisher.stopAllTracks();
expect(track!.stop).toHaveBeenCalled();
expect(publisher['clonedTracks'].size).toBe(0);
});
});
});
Loading