Skip to content
Merged
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
74 changes: 68 additions & 6 deletions packages/media-signaling/src/lib/services/webrtc/Processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,18 @@ export class MediaCallWebRTCProcessor implements IWebRTCProcessor {
await this.initialization;

if (!this.addedEmptyTransceiver) {
this.config.logger?.debug('MediaCallWebRTCProcessor.createOffer.addEmptyTransceiver');
// If there's no audio transceivers yet, add a new one; since it's an offer, the track can be set later
const transceivers = this.peer
.getTransceivers()
.filter((transceiver) => transceiver.sender.track?.kind === 'audio' || transceiver.receiver.track?.kind === 'audio');
const transceivers = this.getAudioTransceivers();

if (!transceivers.length) {
this.peer.addTransceiver('audio', { direction: 'sendrecv' });
this.addedEmptyTransceiver = true;
}
}

this.updateAudioDirectionBeforeNegotiation();

if (iceRestart) {
this.restartIce();
}
Expand Down Expand Up @@ -169,9 +170,7 @@ export class MediaCallWebRTCProcessor implements IWebRTCProcessor {

await this.initialization;

const transceivers = this.peer
.getTransceivers()
.filter((transceiver) => transceiver.sender.track?.kind === 'audio' || transceiver.receiver.track?.kind === 'audio');
const transceivers = this.getAudioTransceivers();

if (!transceivers.length) {
throw new Error('no-audio-transceiver');
Expand All @@ -193,6 +192,10 @@ export class MediaCallWebRTCProcessor implements IWebRTCProcessor {
}

await this.peer.setLocalDescription(sdp);

if (sdp.type === 'answer') {
this.updateAudioDirectionAfterNegotiation();
}
}

public async setRemoteDescription(sdp: RTCSessionDescriptionInit): Promise<void> {
Expand All @@ -207,7 +210,15 @@ export class MediaCallWebRTCProcessor implements IWebRTCProcessor {
throw new Error('unsupported-description-type');
}

if (sdp.type === 'offer') {
this.updateAudioDirectionBeforeNegotiation();
}

await this.peer.setRemoteDescription(sdp);

if (sdp.type === 'answer') {
this.updateAudioDirectionAfterNegotiation();
}
}

public getInternalState<K extends keyof WebRTCInternalStateMap>(stateName: K): ServiceStateValue<WebRTCInternalStateMap, K> {
Expand Down Expand Up @@ -298,6 +309,57 @@ export class MediaCallWebRTCProcessor implements IWebRTCProcessor {
this.emitter.emit('internalStateChange', stateName);
}

private updateAudioDirectionBeforeNegotiation(): void {
// Before the negotiation, we set the direction based on our own state only
// We'll tell the SDK that we want to send audio and, depending on the "on hold" state, also receive it
const desiredDirection = this.held ? 'sendonly' : 'sendrecv';

const transceivers = this.getAudioTransceivers();
for (const transceiver of transceivers) {
if (transceiver.direction === 'stopped') {
continue;
}

if (transceiver.direction !== desiredDirection) {
this.config.logger?.debug(`Changing audio direction from ${transceiver.direction} to ${desiredDirection}`);
}

transceiver.direction = desiredDirection;
}
}

private updateAudioDirectionAfterNegotiation(): void {
// Before the negotiation started, we told the browser we wanted to send audio - but we don't care if actually send or not, it's up to the other side to determine if they want to receive.
// If the other side doesn't want to receive audio, the negotiation will result in a state where "direction" and "currentDirection" don't match
// But if the only difference is that we said we want to send audio and are not sending it, then we can change what we say we want to reflect the current state

// If we didn't do this, everything would still work, but the browser would trigger redundant renegotiations whenever the directions mismatch

const desiredDirection = this.held ? 'sendonly' : 'sendrecv';
const acceptableDirection = this.held ? 'inactive' : 'recvonly';

const transceivers = this.getAudioTransceivers();
for (const transceiver of transceivers) {
if (transceiver.direction !== desiredDirection) {
continue;
}
if (!transceiver.currentDirection || ['stopped', desiredDirection].includes(transceiver.currentDirection)) {
continue;
}

if (transceiver.currentDirection === acceptableDirection) {
this.config.logger?.debug(`Changing audio direction from ${transceiver.direction} to match ${transceiver.currentDirection}.`);
transceiver.direction = transceiver.currentDirection;
}
}
}

private getAudioTransceivers(): RTCRtpTransceiver[] {
return this.peer
.getTransceivers()
.filter((transceiver) => transceiver.sender.track?.kind === 'audio' || transceiver.receiver.track?.kind === 'audio');
}

private registerPeerEvents() {
const { peer } = this;

Expand Down
Loading