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
12 changes: 7 additions & 5 deletions packages/media-signaling/src/lib/Call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -571,16 +571,18 @@ export class ClientMediaCall implements IClientMediaCall {
}

const hadVideoTrack = this.hasScreenVideoTrack();
if (hadVideoTrack && newVideoTrack !== this.screenVideoTrack) {
this.config.logger?.debug('ClientMediaCall.setScreenVideoTrack.stopOldTrack');
this.screenVideoTrack?.stop();
}

const oldVideoTrack = this.screenVideoTrack;
this.screenVideoTrack = newVideoTrack;
if (this.webrtcProcessor) {
await this.webrtcProcessor.setScreenVideoTrack(newVideoTrack);
}
Comment thread
pierre-lehnen-rc marked this conversation as resolved.

// Only stop the track after we replaced it on the transceiver, as we don't want the transceiver to stop if there's another track
if (hadVideoTrack && newVideoTrack !== oldVideoTrack) {
this.config.logger?.debug('ClientMediaCall.setScreenVideoTrack.stopOldTrack');
oldVideoTrack?.stop();
}

if (newVideoTrack && !hadVideoTrack) {
await this.negotiationManager.processNegotiations();
}
Expand Down
64 changes: 51 additions & 13 deletions packages/media-signaling/src/lib/media/MediaStreamWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ export class MediaStreamWrapper implements IMediaStreamWrapper {

private videoTrack: MediaStreamTrackWrapper | null = null;

private audioSender: RTCRtpSender | null = null;
private audioTransceiver: RTCRtpTransceiver | null = null;

private videoSender: RTCRtpSender | null = null;
private videoTransceiver: RTCRtpTransceiver | null = null;

private stopped = false;

Expand Down Expand Up @@ -197,17 +197,61 @@ export class MediaStreamWrapper implements IMediaStreamWrapper {
this.emitter.emit('trackChanged', { track: newTrack, kind });
}

private getCurrentTransceiver(kind: MediaStreamTrack['kind']): RTCRtpTransceiver | null {
const transceiver = kind === 'audio' ? this.audioTransceiver : this.videoTransceiver;
if (!transceiver) {
return null;
}

// transceiver.stopped is not available on safari
if ('stopped' in transceiver && transceiver.stopped) {
return null;
}

if (transceiver.currentDirection === 'stopped' || transceiver.direction === 'stopped') {
return null;
}

return transceiver;
}

private setCurrentTransceiver(kind: MediaStreamTrack['kind'], transceiver: RTCRtpTransceiver | null) {
const oldTransceiver = kind === 'audio' ? this.audioTransceiver : this.videoTransceiver;
if ((!oldTransceiver && !transceiver) || oldTransceiver === transceiver) {
return;
}

const action = transceiver ? 'Changed' : 'Removed';

this.logger?.debug(`${action} current ${kind} transceiver for ${this.tag} stream`);
if (kind === 'audio') {
this.audioTransceiver = transceiver;
} else {
this.videoTransceiver = transceiver;
}
}

private async syncTrackChange(kind: MediaStreamTrack['kind'], track: MediaStreamTrack | null): Promise<void> {
if (this.remote) {
return;
}
const sender = kind === 'audio' ? this.audioSender : this.videoSender;
if (sender) {
if (this.stopped || ['closed', 'failed'].includes(this.peer.connectionState)) {
return;
}

const currentTransceiver = this.getCurrentTransceiver(kind);
if (currentTransceiver?.sender) {
// If we already have a sender of the same kind for this stream, we can just replace the track with no issues
// TODO: safe guard against edge cases where this would fail (eg: changing number of audio channels or increasing video quality)
this.logger?.debug('MediaStreamWrapper.setPeerTrack.replaceTrack', kind);
await sender.replaceTrack(track);
return;
try {
await currentTransceiver.sender.replaceTrack(track);
// Only return early if the track was successfully replaced on a transceiver - otherwise we add it to a new one
return;
} catch (err) {
Comment thread
pierre-lehnen-rc marked this conversation as resolved.
this.logger?.error('MediaStreamWrapper.setPeerTrack.replaceTrack failed', kind, err);
Comment thread
pierre-lehnen-rc marked this conversation as resolved.
this.setCurrentTransceiver(kind, null);
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
}
}

if (!track) {
Expand All @@ -219,13 +263,7 @@ export class MediaStreamWrapper implements IMediaStreamWrapper {
this.peer.addTrack(track, this.stream);

const transceiver = this.peer.getTransceivers().find((t) => t.sender.track === track);
if (transceiver) {
if (kind === 'audio') {
this.audioSender = transceiver.sender;
} else {
this.videoSender = transceiver.sender;
}
}
this.setCurrentTransceiver(kind, transceiver ?? null);
}

private wrapTrack(kind: MediaStreamTrack['kind'], track: MediaStreamTrack | null) {
Expand Down
22 changes: 22 additions & 0 deletions packages/media-signaling/src/lib/services/webrtc/Processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,16 @@ export class MediaCallWebRTCProcessor implements IWebRTCProcessor {
acceptableDirection: RTCRtpTransceiverDirection,
): void {
const transceivers = this.getTransceivers(kind);
let hasAnyValidTransceiver = false;
let hasAnyStoppedTransceiver = false;
for (const transceiver of transceivers) {
if (transceiver.currentDirection === 'stopped') {
hasAnyStoppedTransceiver = true;
continue;
}

hasAnyValidTransceiver = true;

if (transceiver.direction !== desiredDirection) {
continue;
}
Expand All @@ -393,6 +402,19 @@ export class MediaCallWebRTCProcessor implements IWebRTCProcessor {
transceiver.direction = transceiver.currentDirection;
}
}

if (desiredDirection.includes('send') && !hasAnyValidTransceiver && hasAnyStoppedTransceiver) {
this.reactToStoppedTransceiver(kind);
}
}

private reactToStoppedTransceiver(kind: 'audio' | 'video') {
this.config.logger?.error(`The ${kind} transceiver has stopped`);
if (kind === 'video' && this.screenVideoTrack) {
void this.streams.screenShareLocal.setTrack(kind, null).catch((err) => {
this.config.logger?.error('Failed to remove track from screen share media stream', err);
});
}
}

private requestDirection(
Expand Down
Loading