Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WDA-2377] Fix mishandled ringback #767

Merged
merged 5 commits into from
May 28, 2024
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@wazo/sdk",
"version": "0.42.0",
"version": "0.43.0-rc.0",
"description": "Wazo's JavaScript Software Development Kit.",
"main": "index.js",
"types": "lib/types/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/web-rtc-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jest.mock('sip.js/lib/api/user-agent', () => ({
},
}));

const client = new WebRTCClient({} as any, null, undefined);
const client = new WebRTCClient({} as any, undefined, undefined);

describe('WebRTC client', () => {
it('should compute muted/unmuted state', async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/domain/Phone/CTIPhone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ export default class CTIPhone extends Emitter implements Phone {

changeVideoInputDevice() { return Promise.resolve(null); }

changeAudioDevice() {}
changeAudioDevice() { return Promise.resolve(); }

changeRingDevice() {}

Expand Down
2 changes: 1 addition & 1 deletion src/domain/Phone/Phone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export type AvailablePhoneOptions = {
};
export interface Phone extends IEmitter {
accept(callSession: CallSession, enableVideo?: boolean): Promise<string | null>;
changeAudioDevice(id: string): PhoneVoid;
changeAudioDevice(id: string): Promise<void>;
changeRingDevice(id: string): PhoneVoid;
changeAudioVolume(volume: number): PhoneVoid;
changeRingVolume(volume: number): PhoneVoid;
Expand Down
8 changes: 4 additions & 4 deletions src/domain/Phone/WebRTCPhone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,12 +612,12 @@ export default class WebRTCPhone extends Emitter implements Phone {
return callSession;
}

changeAudioDevice(id: string) {
async changeAudioDevice(id: string) {
logger.info('WebRTC phone - change audio device', {
deviceId: id,
});
this.audioOutputDeviceId = id;
this.client.changeAudioOutputDevice(id);
return this.client.changeAudioOutputDevice(id);
}

createAudioElementFor(sessionId: string) {
Expand Down Expand Up @@ -1528,7 +1528,7 @@ export default class WebRTCPhone extends Emitter implements Phone {
this.eventEmitter.emit.apply(this.eventEmitter, [this.client.ON_REINVITE, ...args]);
}, 2000);
});
this.client.on(this.client.ACCEPTED, (sipSession: WazoSession) => {
this.client.on(this.client.ACCEPTED, async (sipSession: WazoSession) => {
const sessionId = this.getSipSessionId(sipSession);
const hasSession = (sessionId in this.callSessions);
logger.info('WebRTC call accepted', {
Expand All @@ -1546,7 +1546,7 @@ export default class WebRTCPhone extends Emitter implements Phone {
this._onCallAccepted(sipSession, this.client.hasVideo(sessionId));

if (this.audioOutputDeviceId) {
this.client.changeAudioOutputDevice(this.audioOutputDeviceId);
await this.client.changeAudioOutputDevice(this.audioOutputDeviceId);
}
});
this.client.on('ended', () => {});
Expand Down
11 changes: 4 additions & 7 deletions src/domain/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,21 +243,18 @@ export type WebRtcConfig = {
media?: MediaConfig;
iceCheckingTimeout?: number;
log?: Record<string, any>;
audioOutputDeviceId?: string;
audioOutputVolume?: number;
userAgentString?: string;
heartbeatDelay?: number;
heartbeatTimeout?: number;
maxHeartbeats?: number;
skipRegister?: boolean;
userUuid?: string;
userUuid?: string,
uaConfigOverrides?: UserAgentConfigOverrides,
audioDeviceOutput?: string,
audioDeviceRing?: string
}; // @see https://github.com/onsip/SIP.js/blob/master/src/Web/Simple.js

export type ConnectionOptions = WebRtcConfig & {
uaConfigOverrides: UserAgentConfigOverrides;
audioDeviceOutput: string;
audioDeviceRing: string;
};
export type IncomingResponse = SipIncomingResponse & { session: any };

export type PeerConnection = RTCPeerConnection & {
Expand Down
8 changes: 4 additions & 4 deletions src/simple/Phone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import IssueReporter from '../service/IssueReporter';
import Emitter from '../utils/Emitter';
import Wazo from './index';
import SFUNotAvailableError from '../domain/SFUNotAvailableError';
import { ConnectionOptions, WazoSession } from '../domain/types';
import { WazoSession, WebRtcConfig } from '../domain/types';

const logger = IssueReporter.loggerFor('simple-phone');
const sipLogger = IssueReporter.loggerFor('sip.js');
Expand Down Expand Up @@ -127,7 +127,7 @@ export class Phone extends Emitter {
this.SessionState = SessionState;
}

async connect(rawOptions: Partial<ConnectionOptions> = {}, sipLine: SipLine | null | undefined = null): Promise<void> {
async connect(rawOptions: Partial<WebRtcConfig> = {}, sipLine: SipLine | null | undefined = null): Promise<void> {
const options = rawOptions;
if (this.phone) {
// Already connected
Expand Down Expand Up @@ -162,7 +162,7 @@ export class Phone extends Emitter {
server: string,
sipLine: SipLine,
displayName: string,
rawOptions: Partial<ConnectionOptions> = {},
rawOptions: Partial<WebRtcConfig> = {},
): void {
if (this.phone) {
// Already connected
Expand Down Expand Up @@ -194,7 +194,7 @@ export class Phone extends Emitter {
password: sipLine.secret,
uri: `${sipLine.username}@${server}`,
...options,
}, null, options.uaConfigOverrides);
}, undefined, options.uaConfigOverrides);
this.phone = new WebRTCPhone(this.client, options.audioDeviceOutput, true, options.audioDeviceRing);

this._transferEvents();
Expand Down
4 changes: 2 additions & 2 deletions src/simple/room/Room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,13 +492,13 @@ class Room extends Emitter {
// Listen to REINVITE to ba able to map msid after upgrading to video in a audio only conference
// This allow to map msid with the non parsed (eg without the `stripVideo` modifier) SDP
Wazo.Phone.phone.on(Wazo.Phone.phone.client.ON_REINVITE, this._boundOnReinvite);
this.on(this.ON_AUDIO_STREAM, stream => {
this.on(this.ON_AUDIO_STREAM, async stream => {
logger.info('on room audio stream');
this.audioStream = stream;

if (!this.roomAudioElement) {
const sessionId = Wazo.Phone.phone?.getSipSessionId(Wazo.Phone.phone?.currentSipSession as WazoSession);
this.roomAudioElement = Wazo.Phone.phone?.createAudioElementFor(sessionId as string) as HTMLAudioElement;
this.roomAudioElement = await Wazo.Phone.phone?.createAudioElementFor(sessionId as string) as HTMLAudioElement;
this.roomAudioElement.srcObject = stream;
} else {
this.roomAudioElement.srcObject = stream;
Expand Down
46 changes: 20 additions & 26 deletions src/web-rtc-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export default class WebRTCClient extends Emitter {

audio: MediaTrackConstraintSet | boolean | undefined;

audioElements: Record<string, HTMLAudioElement & { setSinkId?: (id: string) => void }>;
audioElements: Record<string, HTMLAudioElement>;

video: MediaTrackConstraintSet | boolean | undefined;

Expand Down Expand Up @@ -182,7 +182,7 @@ export default class WebRTCClient extends Emitter {
return [];
}

constructor(config: WebRtcConfig, session: WazoSession | null | undefined, uaConfigOverrides?: UserAgentConfigOverrides) {
constructor(config: WebRtcConfig, session?: WazoSession, uaConfigOverrides?: UserAgentConfigOverrides) {
super();

// For debug purpose
Expand All @@ -198,7 +198,7 @@ export default class WebRTCClient extends Emitter {
this.userAgent = this.createUserAgent(uaConfigOverrides);
});

this.audioOutputDeviceId = config.audioOutputDeviceId;
this.audioOutputDeviceId = config.audioDeviceOutput;
this.audioOutputVolume = config.audioOutputVolume || 1;

if (config.media) {
Expand Down Expand Up @@ -1246,20 +1246,14 @@ export default class WebRTCClient extends Emitter {
this.audioOutputVolume = volume;
}

changeAudioOutputDevice(id: string) {
async changeAudioOutputDevice(id: string) {
logger.info('Changing audio output device', {
id,
});
this.audioOutputDeviceId = id;
Object.values(this.audioElements).forEach(audioElement => {
// `setSinkId` method is not included in any flow type definitions for HTMLAudioElements but is a valid method
// audioElement is an array of HTMLAudioElements, and HTMLAudioElement inherits the method from HTMLMediaElement
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLAudioElement
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId
if (audioElement.setSinkId) {
audioElement.setSinkId(id);
}
});
const promises: Promise<void>[] = [];
Object.values(this.audioElements).forEach(audioElement => promises.push(audioElement.setSinkId(id)));
await Promise.allSettled(promises);
}

async changeAudioInputDevice(id: string, session: WazoSession | null | undefined, force?: boolean): Promise<MediaStream | null> {
Expand Down Expand Up @@ -1735,7 +1729,7 @@ export default class WebRTCClient extends Emitter {
}
}

updateLocalStream(sipSessionId: string, newStream: MediaStream): void {
async updateLocalStream(sipSessionId: string, newStream: MediaStream): Promise<void> {
const sipSession = this.sipSessions[sipSessionId];

if (!sipSession) {
Expand All @@ -1756,7 +1750,7 @@ export default class WebRTCClient extends Emitter {
this.setLocalMediaStream(sipSessionId, newStream);

// Update the local video element
this._setupMedias(sipSession, newStream);
await this._setupMedias(sipSession, newStream);
}

updateRemoteStream(sessionId: string, allTracks = true): void {
Expand Down Expand Up @@ -1832,14 +1826,14 @@ export default class WebRTCClient extends Emitter {
return sessionId in this.conferences;
}

createAudioElementFor(sessionId: string): HTMLAudioElement {
async createAudioElementFor(sessionId: string): Promise<HTMLAudioElement> {
const audio: HTMLAudioElement = document.createElement('audio');
audio.setAttribute('id', `audio-${sessionId}`);

logger.info('creating audio element', { sessionId, audioOutputDeviceId: this.audioOutputDeviceId });

if ((audio as any).setSinkId && this.audioOutputDeviceId) {
(audio as any).setSinkId(this.audioOutputDeviceId);
if (this.audioOutputDeviceId) {
await audio.setSinkId(this.audioOutputDeviceId);
}

if (document.body) {
Expand Down Expand Up @@ -2135,7 +2129,7 @@ export default class WebRTCClient extends Emitter {
oldInviteRequest(request);
};

session.delegate.onInvite = (request: IncomingRequestMessage) => {
session.delegate.onInvite = async (request: IncomingRequestMessage) => {
let updatedCalleeName: string | null = null;
let updatedNumber = null;

Expand Down Expand Up @@ -2165,14 +2159,14 @@ export default class WebRTCClient extends Emitter {

this.updateRemoteStream(sipSessionId, false);

this._setupMedias(session);
await this._setupMedias(session);

return this.eventEmitter.emit(ON_REINVITE, session, request, updatedCalleeName, updatedNumber, hadRemoteVideo);
};
}

_onProgress(session: WazoSession, early = false): void {
this._setupMedias(session);
async _onProgress(session: WazoSession, early = false): Promise<void> {
await this._setupMedias(session);

const sessionId = this.getSipSessionId(session).substr(0, 20);
this.updateRemoteStream(sessionId);
Expand All @@ -2183,15 +2177,15 @@ export default class WebRTCClient extends Emitter {
this.eventEmitter.emit(early ? ON_EARLY_MEDIA : ON_PROGRESS, session);
}

_onAccepted(session: WazoSession, sessionDialog?: SessionDialog, withEvent = true, initAllTracks = true): void {
async _onAccepted(session: WazoSession, sessionDialog?: SessionDialog, withEvent = true, initAllTracks = true): Promise<void> {
logger.info('on call accepted', {
id: session.id,
clientId: this.clientId,
remoteTag: session.remoteTag,
});
this.storeSipSession(session);

this._setupMedias(session);
await this._setupMedias(session);

this.updateRemoteStream(this.getSipSessionId(session), initAllTracks);
const pc = (session.sessionDescriptionHandler as SessionDescriptionHandler)?.peerConnection;
Expand Down Expand Up @@ -2253,7 +2247,7 @@ export default class WebRTCClient extends Emitter {
return Boolean(session.sessionDescriptionHandlerModifiersReInvite.find(modifier => modifier === stripVideo));
}

_setupMedias(session: WazoSession, newStream: MediaStream | null | undefined = null): void {
async _setupMedias(session: WazoSession, newStream: MediaStream | null | undefined = null): Promise<void> {
if (!this._isWeb()) {
logger.info('Setup media on mobile, no need to setup html element, bailing');
return;
Expand All @@ -2271,7 +2265,7 @@ export default class WebRTCClient extends Emitter {
}

if (this._hasAudio() && this._isWeb() && !(sessionId in this.audioElements)) {
this.createAudioElementFor(sessionId);
await this.createAudioElementFor(sessionId);
}

const audioElement = this.audioElements[sessionId];
Expand Down