diff --git a/package.json b/package.json index c641ca61..024d6b73 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/__tests__/web-rtc-client.test.ts b/src/__tests__/web-rtc-client.test.ts index 7f5cef49..9e89de52 100644 --- a/src/__tests__/web-rtc-client.test.ts +++ b/src/__tests__/web-rtc-client.test.ts @@ -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 () => { diff --git a/src/domain/Phone/CTIPhone.ts b/src/domain/Phone/CTIPhone.ts index e7beb581..44c35f8d 100644 --- a/src/domain/Phone/CTIPhone.ts +++ b/src/domain/Phone/CTIPhone.ts @@ -300,7 +300,7 @@ export default class CTIPhone extends Emitter implements Phone { changeVideoInputDevice() { return Promise.resolve(null); } - changeAudioDevice() {} + changeAudioDevice() { return Promise.resolve(); } changeRingDevice() {} diff --git a/src/domain/Phone/Phone.ts b/src/domain/Phone/Phone.ts index d2c69cca..81a48521 100644 --- a/src/domain/Phone/Phone.ts +++ b/src/domain/Phone/Phone.ts @@ -31,7 +31,7 @@ export type AvailablePhoneOptions = { }; export interface Phone extends IEmitter { accept(callSession: CallSession, enableVideo?: boolean): Promise; - changeAudioDevice(id: string): PhoneVoid; + changeAudioDevice(id: string): Promise; changeRingDevice(id: string): PhoneVoid; changeAudioVolume(volume: number): PhoneVoid; changeRingVolume(volume: number): PhoneVoid; diff --git a/src/domain/Phone/WebRTCPhone.ts b/src/domain/Phone/WebRTCPhone.ts index 51a1c390..34e4b753 100644 --- a/src/domain/Phone/WebRTCPhone.ts +++ b/src/domain/Phone/WebRTCPhone.ts @@ -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) { @@ -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', { @@ -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', () => {}); diff --git a/src/domain/types.ts b/src/domain/types.ts index 98fe7118..0e77584f 100644 --- a/src/domain/types.ts +++ b/src/domain/types.ts @@ -243,21 +243,18 @@ export type WebRtcConfig = { media?: MediaConfig; iceCheckingTimeout?: number; log?: Record; - 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 & { diff --git a/src/simple/Phone.ts b/src/simple/Phone.ts index 7634f5b4..c41b2f92 100644 --- a/src/simple/Phone.ts +++ b/src/simple/Phone.ts @@ -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'); @@ -127,7 +127,7 @@ export class Phone extends Emitter { this.SessionState = SessionState; } - async connect(rawOptions: Partial = {}, sipLine: SipLine | null | undefined = null): Promise { + async connect(rawOptions: Partial = {}, sipLine: SipLine | null | undefined = null): Promise { const options = rawOptions; if (this.phone) { // Already connected @@ -162,7 +162,7 @@ export class Phone extends Emitter { server: string, sipLine: SipLine, displayName: string, - rawOptions: Partial = {}, + rawOptions: Partial = {}, ): void { if (this.phone) { // Already connected @@ -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(); diff --git a/src/simple/room/Room.ts b/src/simple/room/Room.ts index 522f4da3..b52708a7 100644 --- a/src/simple/room/Room.ts +++ b/src/simple/room/Room.ts @@ -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; diff --git a/src/web-rtc-client.ts b/src/web-rtc-client.ts index 283ba0b4..9be0cfd8 100644 --- a/src/web-rtc-client.ts +++ b/src/web-rtc-client.ts @@ -90,7 +90,7 @@ export default class WebRTCClient extends Emitter { audio: MediaTrackConstraintSet | boolean | undefined; - audioElements: Record void }>; + audioElements: Record; video: MediaTrackConstraintSet | boolean | undefined; @@ -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 @@ -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) { @@ -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[] = []; + 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 { @@ -1735,7 +1729,7 @@ export default class WebRTCClient extends Emitter { } } - updateLocalStream(sipSessionId: string, newStream: MediaStream): void { + async updateLocalStream(sipSessionId: string, newStream: MediaStream): Promise { const sipSession = this.sipSessions[sipSessionId]; if (!sipSession) { @@ -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 { @@ -1832,14 +1826,14 @@ export default class WebRTCClient extends Emitter { return sessionId in this.conferences; } - createAudioElementFor(sessionId: string): HTMLAudioElement { + async createAudioElementFor(sessionId: string): Promise { 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) { @@ -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; @@ -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 { + await this._setupMedias(session); const sessionId = this.getSipSessionId(session).substr(0, 20); this.updateRemoteStream(sessionId); @@ -2183,7 +2177,7 @@ 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 { logger.info('on call accepted', { id: session.id, clientId: this.clientId, @@ -2191,7 +2185,7 @@ export default class WebRTCClient extends Emitter { }); this.storeSipSession(session); - this._setupMedias(session); + await this._setupMedias(session); this.updateRemoteStream(this.getSipSessionId(session), initAllTracks); const pc = (session.sessionDescriptionHandler as SessionDescriptionHandler)?.peerConnection; @@ -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 { if (!this._isWeb()) { logger.info('Setup media on mobile, no need to setup html element, bailing'); return; @@ -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];