@wazo/sdk 0.43.6 → 1.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/wazo-sdk.js +3 -3
- package/esm/__tests__/__voice__/call.test.js +72 -0
- package/esm/__tests__/__voice__/softphone.test.js +66 -0
- package/esm/__tests__/api-client.test.js +1 -20
- package/esm/api/auth.js +2 -13
- package/esm/checker/checks/webrtc.js +2 -1
- package/esm/domain/InvalidState.js +8 -0
- package/esm/domain/InvalidStateTransition.js +10 -0
- package/esm/domain/Phone/WebRTCPhone.js +33 -35
- package/esm/simple/Auth.js +12 -6
- package/esm/simple/Phone.js +2 -1
- package/esm/simple/__tests__/Auth.test.js +64 -8
- package/esm/simple/index.js +8 -2
- package/esm/simple/room/Room.js +2 -1
- package/esm/simple/room/SipRoom.js +2 -1
- package/esm/state-machine/call-state-machine.js +114 -0
- package/esm/state-machine/softphone-state-machine.js +39 -0
- package/esm/state-machine/utils.js +40 -0
- package/esm/types/__tests__/__voice__/call.test.d.ts +2 -0
- package/esm/types/__tests__/__voice__/call.test.d.ts.map +1 -0
- package/esm/types/__tests__/__voice__/softphone.test.d.ts +2 -0
- package/esm/types/__tests__/__voice__/softphone.test.d.ts.map +1 -0
- package/esm/types/api/auth.d.ts.map +1 -1
- package/esm/types/checker/checks/webrtc.d.ts.map +1 -1
- package/esm/types/domain/InvalidState.d.ts +6 -0
- package/esm/types/domain/InvalidState.d.ts.map +1 -0
- package/esm/types/domain/InvalidStateTransition.d.ts +7 -0
- package/esm/types/domain/InvalidStateTransition.d.ts.map +1 -0
- package/esm/types/domain/Phone/WebRTCPhone.d.ts +0 -1
- package/esm/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
- package/esm/types/domain/types.d.ts +4 -0
- package/esm/types/domain/types.d.ts.map +1 -1
- package/esm/types/simple/Auth.d.ts.map +1 -1
- package/esm/types/simple/Phone.d.ts.map +1 -1
- package/esm/types/simple/index.d.ts +7 -2
- package/esm/types/simple/index.d.ts.map +1 -1
- package/esm/types/simple/room/Room.d.ts.map +1 -1
- package/esm/types/simple/room/SipRoom.d.ts +1 -1
- package/esm/types/simple/room/SipRoom.d.ts.map +1 -1
- package/esm/types/state-machine/call-state-machine.d.ts +43 -0
- package/esm/types/state-machine/call-state-machine.d.ts.map +1 -0
- package/esm/types/state-machine/softphone-state-machine.d.ts +19 -0
- package/esm/types/state-machine/softphone-state-machine.d.ts.map +1 -0
- package/esm/types/state-machine/utils.d.ts +9 -0
- package/esm/types/state-machine/utils.d.ts.map +1 -0
- package/esm/types/utils/api-requester.d.ts.map +1 -1
- package/esm/types/utils/sdp.d.ts +6 -0
- package/esm/types/utils/sdp.d.ts.map +1 -1
- package/esm/types/utils/sip-logger.d.ts +4 -0
- package/esm/types/utils/sip-logger.d.ts.map +1 -0
- package/esm/types/utils/webrtc.d.ts +40 -0
- package/esm/types/utils/webrtc.d.ts.map +1 -1
- package/esm/types/voice/call.d.ts +110 -0
- package/esm/types/voice/call.d.ts.map +1 -0
- package/esm/types/voice/softphone.d.ts +77 -0
- package/esm/types/voice/softphone.d.ts.map +1 -0
- package/esm/types/web-rtc-client.d.ts +1 -36
- package/esm/types/web-rtc-client.d.ts.map +1 -1
- package/esm/utils/__tests__/api-requester.test.js +0 -17
- package/esm/utils/api-requester.js +1 -9
- package/esm/utils/sdp.js +26 -0
- package/esm/utils/sip-logger.js +32 -0
- package/esm/utils/webrtc.js +465 -0
- package/esm/voice/call.js +527 -0
- package/esm/voice/softphone.js +347 -0
- package/esm/web-rtc-client.js +86 -584
- package/lib/__tests__/__voice__/call.test.js +86 -0
- package/lib/__tests__/__voice__/softphone.test.js +80 -0
- package/lib/__tests__/api-client.test.js +1 -20
- package/lib/api/auth.js +2 -13
- package/lib/checker/checks/webrtc.js +2 -1
- package/lib/domain/InvalidState.js +9 -0
- package/lib/domain/InvalidStateTransition.js +10 -0
- package/lib/domain/Phone/WebRTCPhone.js +33 -35
- package/lib/simple/Auth.js +12 -6
- package/lib/simple/Phone.js +2 -1
- package/lib/simple/__tests__/Auth.test.js +64 -11
- package/lib/simple/index.js +6 -0
- package/lib/simple/room/Room.js +4 -3
- package/lib/simple/room/SipRoom.js +2 -1
- package/lib/state-machine/call-state-machine.js +120 -0
- package/lib/state-machine/softphone-state-machine.js +42 -0
- package/lib/state-machine/utils.js +52 -0
- package/lib/types/__tests__/__voice__/call.test.d.ts +2 -0
- package/lib/types/__tests__/__voice__/call.test.d.ts.map +1 -0
- package/lib/types/__tests__/__voice__/softphone.test.d.ts +2 -0
- package/lib/types/__tests__/__voice__/softphone.test.d.ts.map +1 -0
- package/lib/types/api/auth.d.ts.map +1 -1
- package/lib/types/checker/checks/webrtc.d.ts.map +1 -1
- package/lib/types/domain/InvalidState.d.ts +6 -0
- package/lib/types/domain/InvalidState.d.ts.map +1 -0
- package/lib/types/domain/InvalidStateTransition.d.ts +7 -0
- package/lib/types/domain/InvalidStateTransition.d.ts.map +1 -0
- package/lib/types/domain/Phone/WebRTCPhone.d.ts +0 -1
- package/lib/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
- package/lib/types/domain/types.d.ts +4 -0
- package/lib/types/domain/types.d.ts.map +1 -1
- package/lib/types/simple/Auth.d.ts.map +1 -1
- package/lib/types/simple/Phone.d.ts.map +1 -1
- package/lib/types/simple/index.d.ts +7 -2
- package/lib/types/simple/index.d.ts.map +1 -1
- package/lib/types/simple/room/Room.d.ts.map +1 -1
- package/lib/types/simple/room/SipRoom.d.ts +1 -1
- package/lib/types/simple/room/SipRoom.d.ts.map +1 -1
- package/lib/types/state-machine/call-state-machine.d.ts +43 -0
- package/lib/types/state-machine/call-state-machine.d.ts.map +1 -0
- package/lib/types/state-machine/softphone-state-machine.d.ts +19 -0
- package/lib/types/state-machine/softphone-state-machine.d.ts.map +1 -0
- package/lib/types/state-machine/utils.d.ts +9 -0
- package/lib/types/state-machine/utils.d.ts.map +1 -0
- package/lib/types/utils/api-requester.d.ts.map +1 -1
- package/lib/types/utils/sdp.d.ts +6 -0
- package/lib/types/utils/sdp.d.ts.map +1 -1
- package/lib/types/utils/sip-logger.d.ts +4 -0
- package/lib/types/utils/sip-logger.d.ts.map +1 -0
- package/lib/types/utils/webrtc.d.ts +40 -0
- package/lib/types/utils/webrtc.d.ts.map +1 -1
- package/lib/types/voice/call.d.ts +110 -0
- package/lib/types/voice/call.d.ts.map +1 -0
- package/lib/types/voice/softphone.d.ts +77 -0
- package/lib/types/voice/softphone.d.ts.map +1 -0
- package/lib/types/web-rtc-client.d.ts +1 -36
- package/lib/types/web-rtc-client.d.ts.map +1 -1
- package/lib/utils/__tests__/api-requester.test.js +0 -17
- package/lib/utils/api-requester.js +1 -9
- package/lib/utils/sdp.js +27 -1
- package/lib/utils/sip-logger.js +37 -0
- package/lib/utils/webrtc.js +521 -1
- package/lib/voice/call.js +577 -0
- package/lib/voice/softphone.js +375 -0
- package/lib/web-rtc-client.js +89 -601
- package/package.json +3 -2
- package/src/__tests__/__voice__/call.test.ts +90 -0
- package/src/__tests__/__voice__/softphone.test.ts +82 -0
- package/src/__tests__/api-client.test.ts +1 -22
- package/src/api/auth.ts +3 -25
- package/src/checker/checks/webrtc.ts +2 -1
- package/src/domain/InvalidState.ts +13 -0
- package/src/domain/InvalidStateTransition.ts +17 -0
- package/src/domain/Phone/WebRTCPhone.ts +34 -36
- package/src/domain/types.ts +6 -1
- package/src/simple/Auth.ts +13 -6
- package/src/simple/Phone.ts +2 -1
- package/src/simple/__tests__/Auth.test.ts +87 -9
- package/src/simple/index.ts +9 -2
- package/src/simple/room/Room.ts +2 -1
- package/src/simple/room/SipRoom.ts +2 -1
- package/src/state-machine/call-state-machine.ts +131 -0
- package/src/state-machine/softphone-state-machine.ts +49 -0
- package/src/state-machine/utils.ts +53 -0
- package/src/utils/__tests__/api-requester.test.ts +0 -20
- package/src/utils/api-requester.ts +1 -9
- package/src/utils/sdp.ts +36 -0
- package/src/utils/sip-logger.ts +40 -0
- package/src/utils/webrtc.ts +571 -0
- package/src/voice/call.ts +713 -0
- package/src/voice/softphone.ts +485 -0
- package/src/web-rtc-client.ts +90 -690
package/src/web-rtc-client.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable class-methods-use-this, no-param-reassign, max-classes-per-file, no-underscore-dangle */
|
|
2
2
|
|
|
3
|
-
/* global
|
|
3
|
+
/* global document, navigator */
|
|
4
4
|
import 'webrtc-adapter';
|
|
5
5
|
import type { InviterInviteOptions } from 'sip.js/lib/api/inviter-invite-options';
|
|
6
6
|
import type { InvitationRejectOptions } from 'sip.js/lib/api/invitation-reject-options';
|
|
@@ -9,8 +9,6 @@ import type { SessionByeOptions } from 'sip.js/lib/api/session-bye-options';
|
|
|
9
9
|
import type { InvitationAcceptOptions } from 'sip.js/lib/api/invitation-accept-options';
|
|
10
10
|
import type { SessionDialog } from 'sip.js/lib/core/dialogs/session-dialog';
|
|
11
11
|
import type { IncomingRequestMessage } from 'sip.js/lib/core/messages/incoming-request-message';
|
|
12
|
-
import type { SessionDescriptionHandlerFactoryOptions } from 'sip.js/lib/platform/web/session-description-handler/session-description-handler-factory-options';
|
|
13
|
-
import type { SessionDescriptionHandlerConfiguration } from 'sip.js/lib/platform/web/session-description-handler/session-description-handler-configuration';
|
|
14
12
|
import type { SessionDescriptionHandler } from 'sip.js/lib/platform/web/session-description-handler/session-description-handler';
|
|
15
13
|
import { UserAgentState } from 'sip.js/lib/api/user-agent-state';
|
|
16
14
|
import { Parser } from 'sip.js/lib/core/messages/parser';
|
|
@@ -23,27 +21,18 @@ import { Messager } from 'sip.js/lib/api/messager';
|
|
|
23
21
|
import { RegistererState } from 'sip.js/lib/api/registerer-state';
|
|
24
22
|
import { SessionState } from 'sip.js/lib/api/session-state';
|
|
25
23
|
import { TransportState } from 'sip.js/lib/api/transport-state';
|
|
26
|
-
import { defaultPeerConnectionConfiguration } from 'sip.js/lib/platform/web/session-description-handler/peer-connection-configuration-default';
|
|
27
24
|
import getStats from 'getstats';
|
|
28
25
|
|
|
29
26
|
import { OutgoingByeRequest, OutgoingInviteRequest, OutgoingRequest } from 'sip.js/lib/core';
|
|
30
|
-
import { Inviter, Invitation, Registerer,
|
|
31
|
-
import WazoSessionDescriptionHandler, { wazoMediaStreamFactory } from './lib/WazoSessionDescriptionHandler';
|
|
27
|
+
import { Inviter, Invitation, Registerer, SessionDescriptionHandlerOptions } from 'sip.js/lib/api';
|
|
32
28
|
import Emitter from './utils/Emitter';
|
|
33
29
|
import ApiClient from './api-client';
|
|
34
30
|
import IssueReporter from './service/IssueReporter';
|
|
35
31
|
import Heartbeat from './utils/Heartbeat';
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import
|
|
39
|
-
|
|
40
|
-
// We need to replace 0.0.0.0 to 127.0.0.1 in the sdp to avoid MOH during a createOffer.
|
|
41
|
-
export const replaceLocalIpModifier = (description: Record<string, any>) => Promise.resolve({ // description is immutable... so we have to clone it or the `type` attribute won't be returned.
|
|
42
|
-
...JSON.parse(JSON.stringify(description)),
|
|
43
|
-
sdp: description.sdp.replace('c=IN IP4 0.0.0.0', 'c=IN IP4 127.0.0.1'),
|
|
44
|
-
});
|
|
45
|
-
const SIP_ID_LENGTH = 36;
|
|
46
|
-
const DEFAULT_ICE_TIMEOUT = 3000;
|
|
32
|
+
import { getSipSessionId, makeURI, replaceLocalIpModifier, SIP_ID_LENGTH } from './utils/sdp';
|
|
33
|
+
import type { MediaConfig, UserAgentConfigOverrides, WebRtcConfig, IncomingResponse, PeerConnection, WazoSession, WazoTransport } from './domain/types';
|
|
34
|
+
import { cleanupStream, createUaOptions, fetchNetworkStats, getAudioConstraints, getLocalStream, getLocalTracks, getLocalVideoStream, getMediaConfiguration, getPeerConnection, getRemoteStream, getRemoteTracks, getRemoteVideoStream, getRemoteVideoStreamFromPc, getStreamFromConstraints, getVideoConstraints, hasALocalVideoTrack, hasARemoteVideoTrack, hasAVideoTrack, hasLocalVideo, hasRemoteVideo, hasVideoTrack, isAudioOnly, isVideoRemotelyHeld, isWeb, sessionWantsToDoVideo, setLocalMediaStream, toggleAudio, toggleVideo, updateRemoteStream } from './utils/webrtc';
|
|
35
|
+
|
|
47
36
|
const SEND_STATS_DELAY = 5000;
|
|
48
37
|
const states = ['STATUS_NULL', 'STATUS_NEW', 'STATUS_CONNECTING', 'STATUS_CONNECTED', 'STATUS_COMPLETED'];
|
|
49
38
|
const logger = IssueReporter ? IssueReporter.loggerFor('webrtc-client') : console;
|
|
@@ -67,13 +56,12 @@ const ON_ERROR = 'onError';
|
|
|
67
56
|
const ON_SCREEN_SHARING_REINVITE = 'onScreenSharingReinvite';
|
|
68
57
|
const ON_NETWORK_STATS = 'onNetworkStats';
|
|
69
58
|
const ON_DISCONNECTED = 'onDisconnected';
|
|
59
|
+
|
|
70
60
|
export const events = [REGISTERED, UNREGISTERED, REGISTRATION_FAILED, INVITE];
|
|
71
61
|
export const transportEvents = [CONNECTED, DISCONNECTED, TRANSPORT_ERROR, MESSAGE];
|
|
72
62
|
export class CanceledCallError extends Error {}
|
|
73
63
|
|
|
74
64
|
const MAX_REGISTER_TRIES = 5;
|
|
75
|
-
// setting a 24hr timeout and letting the backend define the actual value
|
|
76
|
-
const NO_ANSWER_TIMEOUT = 60 * 60 * 24; // in seconds
|
|
77
65
|
|
|
78
66
|
export default class WebRTCClient extends Emitter {
|
|
79
67
|
clientId: number;
|
|
@@ -165,23 +153,6 @@ export default class WebRTCClient extends Emitter {
|
|
|
165
153
|
|
|
166
154
|
ON_DISCONNECTED: string;
|
|
167
155
|
|
|
168
|
-
static isAPrivateIp(ip: string): boolean {
|
|
169
|
-
const regex = /^(?:10|127|172\.(?:1[6-9]|2[0-9]|3[01])|192\.168)\..*/;
|
|
170
|
-
return regex.exec(ip) == null;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
static getIceServers(ip: string): Array<{
|
|
174
|
-
urls: Array<string>;
|
|
175
|
-
}> {
|
|
176
|
-
if (WebRTCClient.isAPrivateIp(ip)) {
|
|
177
|
-
return [{
|
|
178
|
-
urls: ['stun:stun.l.google.com:19302', 'stun:stun4.l.google.com:19302'],
|
|
179
|
-
}];
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return [];
|
|
183
|
-
}
|
|
184
|
-
|
|
185
156
|
constructor(config: WebRtcConfig, session?: WazoSession, uaConfigOverrides?: UserAgentConfigOverrides) {
|
|
186
157
|
super();
|
|
187
158
|
|
|
@@ -254,7 +225,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
254
225
|
}
|
|
255
226
|
|
|
256
227
|
createUserAgent(uaConfigOverrides?: UserAgentConfigOverrides): UserAgent {
|
|
257
|
-
const uaOptions = this.
|
|
228
|
+
const uaOptions = createUaOptions(this.config, uaConfigOverrides, this.audio, this.video);
|
|
258
229
|
|
|
259
230
|
logger.info('sdk webrtc, creating UA', {
|
|
260
231
|
uaOptions: { ...uaOptions, authorizationPassword: `${uaOptions?.authorizationPassword?.slice(0, 5)}xxxx` },
|
|
@@ -275,7 +246,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
275
246
|
this._setupSession(invitation);
|
|
276
247
|
|
|
277
248
|
const shouldAutoAnswer = !!invitation.request.getHeader('alert-info');
|
|
278
|
-
this.eventEmitter.emit(INVITE, invitation,
|
|
249
|
+
this.eventEmitter.emit(INVITE, invitation, sessionWantsToDoVideo(invitation), shouldAutoAnswer);
|
|
279
250
|
},
|
|
280
251
|
};
|
|
281
252
|
const ua = new UserAgent(uaOptions);
|
|
@@ -386,7 +357,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
386
357
|
return Promise.resolve();
|
|
387
358
|
}
|
|
388
359
|
|
|
389
|
-
const registerOptions =
|
|
360
|
+
const registerOptions = isWeb() ? {} : {
|
|
390
361
|
extraContactHeaderParams: ['mobility=mobile'],
|
|
391
362
|
};
|
|
392
363
|
|
|
@@ -572,7 +543,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
572
543
|
inviterOptions.sessionDescriptionHandlerModifiersReInvite = [stripVideo];
|
|
573
544
|
}
|
|
574
545
|
|
|
575
|
-
const uri = this.
|
|
546
|
+
const uri = makeURI(number, this.config.host);
|
|
576
547
|
let session: WazoSession | null = null;
|
|
577
548
|
|
|
578
549
|
if (uri) {
|
|
@@ -588,7 +559,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
588
559
|
}
|
|
589
560
|
|
|
590
561
|
if (conference) {
|
|
591
|
-
this.conferences[
|
|
562
|
+
this.conferences[getSipSessionId(session)] = true;
|
|
592
563
|
}
|
|
593
564
|
|
|
594
565
|
const inviteOptions: InviterInviteOptions = {
|
|
@@ -615,7 +586,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
615
586
|
this.eventEmitter.emit(REJECTED, session, response);
|
|
616
587
|
},
|
|
617
588
|
},
|
|
618
|
-
sessionDescriptionHandlerOptions:
|
|
589
|
+
sessionDescriptionHandlerOptions: getMediaConfiguration(enableVideo || false, conference),
|
|
619
590
|
};
|
|
620
591
|
|
|
621
592
|
if (inviteOptions.sessionDescriptionHandlerOptions) {
|
|
@@ -652,7 +623,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
652
623
|
}
|
|
653
624
|
|
|
654
625
|
const options = {
|
|
655
|
-
sessionDescriptionHandlerOptions:
|
|
626
|
+
sessionDescriptionHandlerOptions: getMediaConfiguration(enableVideo || false),
|
|
656
627
|
};
|
|
657
628
|
return this._accept(session, options).then(() => {
|
|
658
629
|
// @ts-ignore: private
|
|
@@ -683,7 +654,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
683
654
|
|
|
684
655
|
this._cleanupMedia(session);
|
|
685
656
|
|
|
686
|
-
delete this.sipSessions[
|
|
657
|
+
delete this.sipSessions[getSipSessionId(session)];
|
|
687
658
|
// Check if Invitation or Inviter (Invitation = incoming call)
|
|
688
659
|
const isInviter = session instanceof Inviter;
|
|
689
660
|
|
|
@@ -707,19 +678,9 @@ export default class WebRTCClient extends Emitter {
|
|
|
707
678
|
return Promise.resolve(null);
|
|
708
679
|
}
|
|
709
680
|
|
|
710
|
-
async getStats(session: WazoSession): Promise<RTCStatsReport | null> {
|
|
711
|
-
const pc = (session.sessionDescriptionHandler as SessionDescriptionHandler)?.peerConnection as RTCPeerConnection;
|
|
712
|
-
|
|
713
|
-
if (!pc) {
|
|
714
|
-
return null;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
return pc.getStats(null);
|
|
718
|
-
}
|
|
719
|
-
|
|
720
681
|
// Fetch and emit an event at `interval` with session network stats
|
|
721
682
|
startNetworkMonitoring(session: WazoSession, interval = 1000): void {
|
|
722
|
-
const sessionId =
|
|
683
|
+
const sessionId = getSipSessionId(session);
|
|
723
684
|
logger.info('starting network inspection', {
|
|
724
685
|
id: sessionId,
|
|
725
686
|
clientId: this.clientId,
|
|
@@ -729,7 +690,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
729
690
|
}
|
|
730
691
|
|
|
731
692
|
stopNetworkMonitoring(session: WazoSession): void {
|
|
732
|
-
const sessionId =
|
|
693
|
+
const sessionId = getSipSessionId(session);
|
|
733
694
|
const exists = (sessionId in this.networkMonitoringInterval);
|
|
734
695
|
logger.info('stopping network inspection', {
|
|
735
696
|
clientId: this.clientId,
|
|
@@ -808,21 +769,12 @@ export default class WebRTCClient extends Emitter {
|
|
|
808
769
|
logger.info('sdk webrtc client closed', { clientId: this.clientId });
|
|
809
770
|
}
|
|
810
771
|
|
|
811
|
-
getNumber(session: Inviter): string | null | undefined {
|
|
812
|
-
if (!session) {
|
|
813
|
-
return null;
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
// @ts-ignore: private
|
|
817
|
-
return session.remoteIdentity.uri._normal.user;
|
|
818
|
-
}
|
|
819
|
-
|
|
820
772
|
mute(session: WazoSession): void {
|
|
821
773
|
logger.info('sdk webrtc mute', {
|
|
822
774
|
id: session.id,
|
|
823
775
|
});
|
|
824
776
|
|
|
825
|
-
|
|
777
|
+
toggleAudio(session, true);
|
|
826
778
|
}
|
|
827
779
|
|
|
828
780
|
unmute(session: WazoSession): void {
|
|
@@ -830,7 +782,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
830
782
|
id: session.id,
|
|
831
783
|
});
|
|
832
784
|
|
|
833
|
-
|
|
785
|
+
toggleAudio(session, false);
|
|
834
786
|
}
|
|
835
787
|
|
|
836
788
|
isAudioMuted(session: WazoSession): boolean {
|
|
@@ -875,7 +827,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
875
827
|
id: session.id,
|
|
876
828
|
});
|
|
877
829
|
|
|
878
|
-
|
|
830
|
+
toggleVideo(session, false);
|
|
879
831
|
}
|
|
880
832
|
|
|
881
833
|
toggleCameraOff(session: WazoSession): void {
|
|
@@ -883,11 +835,11 @@ export default class WebRTCClient extends Emitter {
|
|
|
883
835
|
id: session.id,
|
|
884
836
|
});
|
|
885
837
|
|
|
886
|
-
|
|
838
|
+
toggleVideo(session, true);
|
|
887
839
|
}
|
|
888
840
|
|
|
889
841
|
hold(session: WazoSession, isConference = false, hadVideo = false): Promise<OutgoingInviteRequest | void> {
|
|
890
|
-
const sessionId =
|
|
842
|
+
const sessionId = getSipSessionId(session);
|
|
891
843
|
const hasVideo = hadVideo || this.hasLocalVideo(sessionId);
|
|
892
844
|
logger.info('sdk webrtc hold', {
|
|
893
845
|
sessionId,
|
|
@@ -920,9 +872,9 @@ export default class WebRTCClient extends Emitter {
|
|
|
920
872
|
hold: true,
|
|
921
873
|
conference: isConference,
|
|
922
874
|
} as SessionDescriptionHandlerOptions;
|
|
923
|
-
const options =
|
|
875
|
+
const options = getMediaConfiguration(false, isConference);
|
|
924
876
|
|
|
925
|
-
if (!
|
|
877
|
+
if (!isWeb()) {
|
|
926
878
|
options.sessionDescriptionHandlerModifiers = [holdModifier];
|
|
927
879
|
}
|
|
928
880
|
|
|
@@ -945,7 +897,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
945
897
|
}
|
|
946
898
|
|
|
947
899
|
unhold(session: WazoSession, isConference = false): Promise<OutgoingInviteRequest | void> {
|
|
948
|
-
const sessionId =
|
|
900
|
+
const sessionId = getSipSessionId(session);
|
|
949
901
|
const hasVideo = sessionId in this.heldSessions && this.heldSessions[sessionId].hasVideo;
|
|
950
902
|
logger.info('sdk webrtc unhold', {
|
|
951
903
|
sessionId,
|
|
@@ -963,14 +915,14 @@ export default class WebRTCClient extends Emitter {
|
|
|
963
915
|
|
|
964
916
|
this.unmute(session);
|
|
965
917
|
|
|
966
|
-
delete this.heldSessions[
|
|
918
|
+
delete this.heldSessions[getSipSessionId(session)];
|
|
967
919
|
session.sessionDescriptionHandlerOptionsReInvite = {
|
|
968
920
|
hold: false,
|
|
969
921
|
conference: isConference,
|
|
970
922
|
} as SessionDescriptionHandlerOptions;
|
|
971
|
-
const options =
|
|
923
|
+
const options = getMediaConfiguration(false, isConference);
|
|
972
924
|
|
|
973
|
-
if (!
|
|
925
|
+
if (!isWeb()) {
|
|
974
926
|
// We should sent an empty `sessionDescriptionHandlerModifiers` or sip.js will take the last sent modifiers
|
|
975
927
|
// (eg: holdModifier)
|
|
976
928
|
options.sessionDescriptionHandlerModifiers = [];
|
|
@@ -987,113 +939,16 @@ export default class WebRTCClient extends Emitter {
|
|
|
987
939
|
});
|
|
988
940
|
}
|
|
989
941
|
|
|
990
|
-
// Returns true if a re-INVITE is required
|
|
991
|
-
async upgradeToVideo(session: WazoSession, constraints: Record<string, any>, isConference: boolean): Promise<MediaStream | undefined> {
|
|
992
|
-
const pc = (session.sessionDescriptionHandler as SessionDescriptionHandler)?.peerConnection;
|
|
993
|
-
// Check if a video sender already exists
|
|
994
|
-
let videoSender;
|
|
995
|
-
|
|
996
|
-
if (isConference) {
|
|
997
|
-
// We search for the last transceiver without `video-` in the mid (video- means remote transceiver)
|
|
998
|
-
const transceivers = pc?.getTransceivers() || [];
|
|
999
|
-
const transceiverIdx = lastIndexOf(transceivers, transceiver => transceiver.sender.track === null && transceiver.mid && transceiver.mid.indexOf('video') === -1);
|
|
1000
|
-
videoSender = transceiverIdx !== -1 ? transceivers[transceiverIdx].sender : null;
|
|
1001
|
-
} else {
|
|
1002
|
-
videoSender = pc && pc.getSenders && pc.getSenders().find((sender) => sender.track === null);
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
if (!videoSender) {
|
|
1006
|
-
// When no video sender found, it means that we're in the first video upgrade in 1:1
|
|
1007
|
-
return;
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
// Reuse bidirectional video stream
|
|
1011
|
-
const newStream = await this.getStreamFromConstraints(constraints);
|
|
1012
|
-
|
|
1013
|
-
if (!newStream) {
|
|
1014
|
-
console.warn(`Can't create media stream with: ${JSON.stringify(constraints || {})}`);
|
|
1015
|
-
return;
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
// Add previous local audio track
|
|
1019
|
-
if (constraints && !constraints.audio) {
|
|
1020
|
-
const localVideoStream: MediaStream = (session.sessionDescriptionHandler as SessionDescriptionHandler)?.localMediaStream;
|
|
1021
|
-
const localAudioTrack = localVideoStream.getTracks().find(track => track.kind === 'audio');
|
|
1022
|
-
|
|
1023
|
-
if (localAudioTrack) {
|
|
1024
|
-
newStream.addTrack(localAudioTrack);
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
const videoTrack = newStream.getVideoTracks()[0];
|
|
1029
|
-
|
|
1030
|
-
if (videoTrack) {
|
|
1031
|
-
videoSender.replaceTrack(videoTrack);
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
this.setLocalMediaStream(this.getSipSessionId(session), newStream);
|
|
1035
|
-
return newStream;
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
downgradeToAudio(session: WazoSession): void {
|
|
1039
|
-
// Release local video stream when downgrading to audio
|
|
1040
|
-
const sessionDescriptionHandler = session.sessionDescriptionHandler as SessionDescriptionHandler;
|
|
1041
|
-
const localStream = sessionDescriptionHandler?.localMediaStream;
|
|
1042
|
-
const pc = sessionDescriptionHandler?.peerConnection;
|
|
1043
|
-
const videoTracks = localStream.getVideoTracks();
|
|
1044
|
-
|
|
1045
|
-
// Remove video senders
|
|
1046
|
-
if (pc?.getSenders) {
|
|
1047
|
-
pc.getSenders().filter((sender) => sender.track && sender.track.kind === 'video').forEach((videoSender) => {
|
|
1048
|
-
videoSender.replaceTrack(null);
|
|
1049
|
-
});
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
videoTracks.forEach((videoTrack: MediaStreamTrack) => {
|
|
1053
|
-
videoTrack.enabled = false;
|
|
1054
|
-
videoTrack.stop();
|
|
1055
|
-
localStream.removeTrack(videoTrack);
|
|
1056
|
-
});
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
async getStreamFromConstraints(constraints: Record<string, any>, conference = false): Promise<MediaStream | null | undefined> {
|
|
1060
|
-
const video = constraints && constraints.video;
|
|
1061
|
-
const {
|
|
1062
|
-
constraints: newConstraints,
|
|
1063
|
-
} = this.getMediaConfiguration(video, conference, constraints);
|
|
1064
|
-
let newStream: MediaStream & { local?: boolean } | null = null;
|
|
1065
|
-
|
|
1066
|
-
try {
|
|
1067
|
-
newStream = await wazoMediaStreamFactory(newConstraints);
|
|
1068
|
-
} catch (e: any) { // Nothing to do when the user cancel the screensharing prompt
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
if (!newStream) {
|
|
1072
|
-
return null;
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
newStream.local = true;
|
|
1076
|
-
return newStream;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
942
|
getHeldSession(sessionId: string) {
|
|
1080
943
|
return this.heldSessions[sessionId];
|
|
1081
944
|
}
|
|
1082
945
|
|
|
1083
946
|
isCallHeld(session: WazoSession): boolean {
|
|
1084
|
-
return
|
|
947
|
+
return getSipSessionId(session) in this.heldSessions;
|
|
1085
948
|
}
|
|
1086
949
|
|
|
1087
950
|
isVideoRemotelyHeld(sessionId: string): boolean {
|
|
1088
|
-
|
|
1089
|
-
const sdp = pc && pc.remoteDescription ? pc.remoteDescription.sdp : null;
|
|
1090
|
-
|
|
1091
|
-
if (!sdp) {
|
|
1092
|
-
return false;
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
const videoDirection = getVideoDirection(sdp);
|
|
1096
|
-
return videoDirection === 'sendonly';
|
|
951
|
+
return isVideoRemotelyHeld(this.heldSessions[sessionId]);
|
|
1097
952
|
}
|
|
1098
953
|
|
|
1099
954
|
sendDTMF(session: WazoSession, tone: string): boolean {
|
|
@@ -1102,14 +957,14 @@ export default class WebRTCClient extends Emitter {
|
|
|
1102
957
|
}
|
|
1103
958
|
|
|
1104
959
|
logger.info('Sending DTMF', {
|
|
1105
|
-
id:
|
|
960
|
+
id: getSipSessionId(session),
|
|
1106
961
|
tone,
|
|
1107
962
|
});
|
|
1108
963
|
return session.sessionDescriptionHandler.sendDtmf(tone);
|
|
1109
964
|
}
|
|
1110
965
|
|
|
1111
966
|
message(destination: string, message: string): void {
|
|
1112
|
-
const uri = this.
|
|
967
|
+
const uri = makeURI(destination, this.config.host);
|
|
1113
968
|
if (!this.userAgent || !uri) {
|
|
1114
969
|
logger.warn('Null value on message', { uri, userAgent: this.userAgent });
|
|
1115
970
|
return;
|
|
@@ -1121,7 +976,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
1121
976
|
transfer(session: WazoSession, target: string): void {
|
|
1122
977
|
this.hold(session);
|
|
1123
978
|
logger.info('Transfering a session', {
|
|
1124
|
-
id:
|
|
979
|
+
id: getSipSessionId(session),
|
|
1125
980
|
target,
|
|
1126
981
|
});
|
|
1127
982
|
const options = {
|
|
@@ -1132,7 +987,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
1132
987
|
},
|
|
1133
988
|
};
|
|
1134
989
|
setTimeout(() => {
|
|
1135
|
-
const uri = this.
|
|
990
|
+
const uri = makeURI(target, this.config.host);
|
|
1136
991
|
if (!uri) {
|
|
1137
992
|
logger.warn('transfer timeout: null URI');
|
|
1138
993
|
return;
|
|
@@ -1145,21 +1000,21 @@ export default class WebRTCClient extends Emitter {
|
|
|
1145
1000
|
atxfer(session: WazoSession): Record<string, any> {
|
|
1146
1001
|
this.hold(session);
|
|
1147
1002
|
logger.info('webrtc transfer started', {
|
|
1148
|
-
id:
|
|
1003
|
+
id: getSipSessionId(session),
|
|
1149
1004
|
});
|
|
1150
1005
|
const result: Record<string, any> = {
|
|
1151
1006
|
newSession: null,
|
|
1152
1007
|
init: async (target: string) => {
|
|
1153
1008
|
logger.info('webrtc transfer initialized', {
|
|
1154
|
-
id:
|
|
1009
|
+
id: getSipSessionId(session),
|
|
1155
1010
|
target,
|
|
1156
1011
|
});
|
|
1157
1012
|
result.newSession = await this.call(target);
|
|
1158
1013
|
},
|
|
1159
1014
|
complete: () => {
|
|
1160
1015
|
logger.info('webrtc transfer completed', {
|
|
1161
|
-
id:
|
|
1162
|
-
referId:
|
|
1016
|
+
id: getSipSessionId(session),
|
|
1017
|
+
referId: getSipSessionId(result.newSession),
|
|
1163
1018
|
});
|
|
1164
1019
|
session.refer(result.newSession);
|
|
1165
1020
|
},
|
|
@@ -1205,7 +1060,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
1205
1060
|
|
|
1206
1061
|
const core = this.userAgent?.userAgentCore;
|
|
1207
1062
|
|
|
1208
|
-
const fromURI =
|
|
1063
|
+
const fromURI = makeURI(this.config.authorizationUser || '', this.config.host);
|
|
1209
1064
|
|
|
1210
1065
|
const toURI = new URI('sip', '', this.config.host);
|
|
1211
1066
|
if (fromURI) {
|
|
@@ -1229,10 +1084,6 @@ export default class WebRTCClient extends Emitter {
|
|
|
1229
1084
|
return this.userAgent ? `${this.userAgent.configuration.contactName}/${this.userAgent.contact.uri}` : null;
|
|
1230
1085
|
}
|
|
1231
1086
|
|
|
1232
|
-
isFirefox(): boolean {
|
|
1233
|
-
return this._isWeb() && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
1087
|
changeAudioOutputVolume(volume: number): void {
|
|
1237
1088
|
logger.info('Changing audio output volume', {
|
|
1238
1089
|
volume,
|
|
@@ -1356,13 +1207,13 @@ export default class WebRTCClient extends Emitter {
|
|
|
1356
1207
|
}
|
|
1357
1208
|
|
|
1358
1209
|
changeSessionVideoInputDevice(id: string | null | undefined, session: WazoSession): Promise<MediaStream | void> {
|
|
1359
|
-
if (!
|
|
1210
|
+
if (!sessionWantsToDoVideo(session)) {
|
|
1360
1211
|
return Promise.resolve();
|
|
1361
1212
|
}
|
|
1362
1213
|
|
|
1363
1214
|
const sdh = session.sessionDescriptionHandler as SessionDescriptionHandler;
|
|
1364
1215
|
const pc = sdh.peerConnection;
|
|
1365
|
-
const sessionId =
|
|
1216
|
+
const sessionId = getSipSessionId(session);
|
|
1366
1217
|
const localStream = this.getLocalStream(sessionId);
|
|
1367
1218
|
logger.info('changing video input device', {
|
|
1368
1219
|
id,
|
|
@@ -1406,7 +1257,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
1406
1257
|
|
|
1407
1258
|
// let's update the local stream
|
|
1408
1259
|
this.eventEmitter.emit('onVideoInputChange', stream);
|
|
1409
|
-
this.
|
|
1260
|
+
setLocalMediaStream(this.sipSessions[sessionId], stream);
|
|
1410
1261
|
return stream;
|
|
1411
1262
|
});
|
|
1412
1263
|
}
|
|
@@ -1430,12 +1281,12 @@ export default class WebRTCClient extends Emitter {
|
|
|
1430
1281
|
}
|
|
1431
1282
|
|
|
1432
1283
|
const wasMuted = this.isAudioMuted(sipSession);
|
|
1433
|
-
const shouldDoVideo = newConstraints ? newConstraints.video :
|
|
1284
|
+
const shouldDoVideo = newConstraints ? newConstraints.video : sessionWantsToDoVideo(sipSession);
|
|
1434
1285
|
const shouldDoScreenSharing = newConstraints && newConstraints.screen;
|
|
1435
1286
|
const desktop = newConstraints && newConstraints.desktop;
|
|
1436
1287
|
logger.info('Sending reinvite', {
|
|
1437
1288
|
clientId: this.clientId,
|
|
1438
|
-
id:
|
|
1289
|
+
id: getSipSessionId(sipSession),
|
|
1439
1290
|
newConstraints,
|
|
1440
1291
|
conference,
|
|
1441
1292
|
audioOnly,
|
|
@@ -1457,7 +1308,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
1457
1308
|
} as SessionDescriptionHandlerOptions;
|
|
1458
1309
|
const {
|
|
1459
1310
|
constraints,
|
|
1460
|
-
} =
|
|
1311
|
+
} = getMediaConfiguration(shouldDoVideo, conference, newConstraints);
|
|
1461
1312
|
return sipSession.invite({
|
|
1462
1313
|
requestDelegate: {
|
|
1463
1314
|
onAccept: (response: IncomingResponse) => {
|
|
@@ -1473,11 +1324,11 @@ export default class WebRTCClient extends Emitter {
|
|
|
1473
1324
|
}
|
|
1474
1325
|
|
|
1475
1326
|
logger.info('on re-INVITE accepted', {
|
|
1476
|
-
id:
|
|
1327
|
+
id: getSipSessionId(sipSession),
|
|
1477
1328
|
wasMuted,
|
|
1478
1329
|
shouldDoScreenSharing,
|
|
1479
1330
|
});
|
|
1480
|
-
|
|
1331
|
+
updateRemoteStream(sipSession, false);
|
|
1481
1332
|
|
|
1482
1333
|
if (wasMuted) {
|
|
1483
1334
|
this.mute(sipSession);
|
|
@@ -1516,135 +1367,70 @@ export default class WebRTCClient extends Emitter {
|
|
|
1516
1367
|
}
|
|
1517
1368
|
|
|
1518
1369
|
getPeerConnection(sessionId: string) {
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
if (!sipSession) {
|
|
1522
|
-
return null;
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
return sipSession.sessionDescriptionHandler ? (sipSession.sessionDescriptionHandler as SessionDescriptionHandler).peerConnection : null;
|
|
1370
|
+
return getPeerConnection(this.sipSessions[sessionId]);
|
|
1526
1371
|
}
|
|
1527
1372
|
|
|
1528
1373
|
// Local streams
|
|
1529
1374
|
getLocalStream(sessionId: string): MediaStream | null {
|
|
1530
|
-
|
|
1531
|
-
return (sipSession?.sessionDescriptionHandler as SessionDescriptionHandler)?.localMediaStream || null;
|
|
1375
|
+
return getLocalStream(this.sipSessions[sessionId]);
|
|
1532
1376
|
}
|
|
1533
1377
|
|
|
1534
1378
|
getLocalTracks(sessionId: string): MediaStreamTrack[] {
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
if (!localStream) {
|
|
1538
|
-
return [];
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
return localStream.getTracks();
|
|
1379
|
+
return getLocalTracks(this.sipSessions[sessionId]);
|
|
1542
1380
|
}
|
|
1543
1381
|
|
|
1544
1382
|
hasLocalVideo(sessionId: string): boolean {
|
|
1545
|
-
return this.
|
|
1383
|
+
return hasLocalVideo(this.sipSessions[sessionId]);
|
|
1546
1384
|
}
|
|
1547
1385
|
|
|
1548
1386
|
// Check if we have at least one video track, even muted or not live
|
|
1549
1387
|
hasALocalVideoTrack(sessionId: string): boolean {
|
|
1550
|
-
return this.
|
|
1388
|
+
return hasALocalVideoTrack(this.sipSessions[sessionId]);
|
|
1551
1389
|
}
|
|
1552
1390
|
|
|
1553
1391
|
getLocalVideoStream(sessionId: string): MediaStream | null | undefined {
|
|
1554
|
-
return
|
|
1392
|
+
return getLocalVideoStream(this.sipSessions[sessionId]);
|
|
1555
1393
|
}
|
|
1556
1394
|
|
|
1557
1395
|
// Remote streams
|
|
1558
1396
|
getRemoteStream(sessionId: string): MediaStream | null {
|
|
1559
|
-
|
|
1560
|
-
return (sipSession?.sessionDescriptionHandler as SessionDescriptionHandler)?.remoteMediaStream || null;
|
|
1397
|
+
return getRemoteStream(this.sipSessions[sessionId]);
|
|
1561
1398
|
}
|
|
1562
1399
|
|
|
1563
1400
|
getRemoteTracks(sessionId: string): MediaStreamTrack[] {
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
if (!remoteStream) {
|
|
1567
|
-
return [];
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
return remoteStream.getTracks();
|
|
1401
|
+
return getRemoteTracks(this.sipSessions[sessionId]);
|
|
1571
1402
|
}
|
|
1572
1403
|
|
|
1573
1404
|
hasRemoteVideo(sessionId: string): boolean {
|
|
1574
|
-
return this.
|
|
1405
|
+
return hasRemoteVideo(this.sipSessions[sessionId]);
|
|
1575
1406
|
}
|
|
1576
1407
|
|
|
1577
1408
|
// Check if we have at least one video track, even muted or not live
|
|
1578
1409
|
hasARemoteVideoTrack(sessionId: string): boolean {
|
|
1579
|
-
return this.
|
|
1410
|
+
return hasARemoteVideoTrack(this.sipSessions[sessionId]);
|
|
1580
1411
|
}
|
|
1581
1412
|
|
|
1582
1413
|
getRemoteVideoStream(sessionId: string): MediaStream | null | undefined {
|
|
1583
|
-
|
|
1584
|
-
return null;
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
return this.hasRemoteVideo(sessionId) ? this.getRemoteStream(sessionId) : null;
|
|
1414
|
+
return getRemoteVideoStream(this.sipSessions[sessionId]);
|
|
1588
1415
|
}
|
|
1589
1416
|
|
|
1590
1417
|
// Useful in a react-native environment when remoteMediaStream is not updated
|
|
1591
1418
|
getRemoteVideoStreamFromPc(sessionId: string): MediaStream | null | undefined {
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
if (!pc) {
|
|
1595
|
-
return null;
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
return pc.getRemoteStreams().find((stream: MediaStream) => !!stream.getVideoTracks().length);
|
|
1419
|
+
return getRemoteVideoStreamFromPc(this.sipSessions[sessionId]);
|
|
1599
1420
|
}
|
|
1600
1421
|
|
|
1601
1422
|
hasVideo(sessionId: string): boolean {
|
|
1602
|
-
return
|
|
1423
|
+
return hasVideoTrack(this.sipSessions[sessionId]);
|
|
1603
1424
|
}
|
|
1604
1425
|
|
|
1605
1426
|
hasAVideoTrack(sessionId: string): boolean {
|
|
1606
|
-
return
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
getSipSessionId(sipSession: WazoSession | null | undefined): string {
|
|
1610
|
-
if (!sipSession) {
|
|
1611
|
-
return '';
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
const message = sipSession.message as Partial<{ callId?: string }>;
|
|
1615
|
-
if (message?.callId) {
|
|
1616
|
-
return message.callId.substring(0, SIP_ID_LENGTH);
|
|
1617
|
-
}
|
|
1618
|
-
|
|
1619
|
-
// For Inviter
|
|
1620
|
-
// @ts-ignore: private
|
|
1621
|
-
if (sipSession instanceof Inviter && sipSession.outgoingRequestMessage) {
|
|
1622
|
-
// @ts-ignore: private
|
|
1623
|
-
return sipSession.outgoingRequestMessage.callId.substring(0, SIP_ID_LENGTH);
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
// For Invitation
|
|
1627
|
-
return (sipSession.id || '').substring(0, SIP_ID_LENGTH);
|
|
1427
|
+
return hasAVideoTrack(this.sipSessions[sessionId]);
|
|
1628
1428
|
}
|
|
1629
1429
|
|
|
1630
1430
|
async waitForRegister(): Promise<void> {
|
|
1631
1431
|
return new Promise(resolve => this.on(REGISTERED, resolve));
|
|
1632
1432
|
}
|
|
1633
1433
|
|
|
1634
|
-
// eslint-disable-next-line no-unused-vars
|
|
1635
|
-
sessionWantsToDoVideo(session: WazoSession): boolean {
|
|
1636
|
-
if (!session) {
|
|
1637
|
-
return false;
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
const {
|
|
1641
|
-
body,
|
|
1642
|
-
} = session.request || session;
|
|
1643
|
-
// Sometimes with InviteClientContext the body is in the body attribute ...
|
|
1644
|
-
const sdp = typeof body === 'object' && body ? body.body : body;
|
|
1645
|
-
return hasAnActiveVideo(sdp);
|
|
1646
|
-
}
|
|
1647
|
-
|
|
1648
1434
|
hasHeartbeat(): boolean {
|
|
1649
1435
|
return this.heartbeat.hasHeartbeat;
|
|
1650
1436
|
}
|
|
@@ -1678,7 +1464,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
1678
1464
|
onCallEnded(session: WazoSession): void {
|
|
1679
1465
|
this._cleanupMedia(session);
|
|
1680
1466
|
|
|
1681
|
-
delete this.sipSessions[
|
|
1467
|
+
delete this.sipSessions[getSipSessionId(session)];
|
|
1682
1468
|
|
|
1683
1469
|
this._stopSendingStats(session);
|
|
1684
1470
|
|
|
@@ -1697,7 +1483,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
1697
1483
|
}
|
|
1698
1484
|
|
|
1699
1485
|
storeSipSession(session: WazoSession): void {
|
|
1700
|
-
const id =
|
|
1486
|
+
const id = getSipSessionId(session);
|
|
1701
1487
|
logger.info('storing sip session', { id, clientId: this.clientId });
|
|
1702
1488
|
|
|
1703
1489
|
this.sipSessions[id] = session;
|
|
@@ -1711,24 +1497,6 @@ export default class WebRTCClient extends Emitter {
|
|
|
1711
1497
|
return Object.keys(this.sipSessions);
|
|
1712
1498
|
}
|
|
1713
1499
|
|
|
1714
|
-
setLocalMediaStream(sipSessionId: string, newStream: MediaStream): void {
|
|
1715
|
-
const sipSession = this.sipSessions[sipSessionId];
|
|
1716
|
-
|
|
1717
|
-
if (!sipSession) {
|
|
1718
|
-
return;
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
|
-
logger.info('setting local media stream', {
|
|
1722
|
-
sipSessionId,
|
|
1723
|
-
tracks: newStream ? newStream.getTracks() : null,
|
|
1724
|
-
});
|
|
1725
|
-
if (sipSession.sessionDescriptionHandler) {
|
|
1726
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
1727
|
-
// @ts-ignore: protected
|
|
1728
|
-
sipSession.sessionDescriptionHandler._localMediaStream = newStream;
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
1500
|
async updateLocalStream(sipSessionId: string, newStream: MediaStream): Promise<void> {
|
|
1733
1501
|
const sipSession = this.sipSessions[sipSessionId];
|
|
1734
1502
|
|
|
@@ -1744,84 +1512,15 @@ export default class WebRTCClient extends Emitter {
|
|
|
1744
1512
|
});
|
|
1745
1513
|
|
|
1746
1514
|
if (oldStream) {
|
|
1747
|
-
|
|
1515
|
+
cleanupStream(oldStream);
|
|
1748
1516
|
}
|
|
1749
1517
|
|
|
1750
|
-
|
|
1518
|
+
setLocalMediaStream(sipSession, newStream);
|
|
1751
1519
|
|
|
1752
1520
|
// Update the local video element
|
|
1753
1521
|
await this._setupMedias(sipSession, newStream);
|
|
1754
1522
|
}
|
|
1755
1523
|
|
|
1756
|
-
updateRemoteStream(sessionId: string, allTracks = true): void {
|
|
1757
|
-
const remoteStream = this.getRemoteStream(sessionId);
|
|
1758
|
-
const pc = this.getPeerConnection(sessionId);
|
|
1759
|
-
logger.info('Updating remote stream', {
|
|
1760
|
-
sessionId,
|
|
1761
|
-
tracks: remoteStream ? remoteStream.getTracks() : null,
|
|
1762
|
-
receiverTracks: pc && pc.getReceivers ? pc.getReceivers().map((receiver) => receiver.track) : null,
|
|
1763
|
-
});
|
|
1764
|
-
|
|
1765
|
-
if (!pc || !remoteStream) {
|
|
1766
|
-
return;
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
remoteStream.getTracks().forEach(track => {
|
|
1770
|
-
if (allTracks || track.kind === 'video') {
|
|
1771
|
-
remoteStream.removeTrack(track);
|
|
1772
|
-
}
|
|
1773
|
-
});
|
|
1774
|
-
|
|
1775
|
-
if (pc.getReceivers) {
|
|
1776
|
-
pc.getReceivers().forEach((receiver) => {
|
|
1777
|
-
if (allTracks || receiver.track.kind === 'video') {
|
|
1778
|
-
remoteStream.addTrack(receiver.track);
|
|
1779
|
-
}
|
|
1780
|
-
});
|
|
1781
|
-
}
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
|
-
getMediaConfiguration(enableVideo: boolean, conference = false, constraints: Record<string, any> | null | undefined = null): Record<string, any> {
|
|
1785
|
-
const screenSharing = constraints && 'screen' in constraints ? constraints.screen : false;
|
|
1786
|
-
const isDesktop = constraints && 'desktop' in constraints ? constraints.desktop : false;
|
|
1787
|
-
const withAudio = constraints && 'audio' in constraints ? constraints.audio : true;
|
|
1788
|
-
const mandatoryVideo = constraints && typeof constraints.video === 'object' ? constraints.video.mandatory : {};
|
|
1789
|
-
logger.info('Retrieving media a configuration', {
|
|
1790
|
-
enableVideo,
|
|
1791
|
-
screenSharing,
|
|
1792
|
-
isDesktop,
|
|
1793
|
-
withAudio,
|
|
1794
|
-
constraints,
|
|
1795
|
-
});
|
|
1796
|
-
return {
|
|
1797
|
-
constraints: {
|
|
1798
|
-
// Exact constraint are not supported with `getDisplayMedia` and we must have a video=false in desktop screenshare
|
|
1799
|
-
audio: screenSharing ? !isDesktop : withAudio ? this._getAudioConstraints() : false,
|
|
1800
|
-
video: screenSharing ? isDesktop ? {
|
|
1801
|
-
mandatory: {
|
|
1802
|
-
chromeMediaSource: 'desktop',
|
|
1803
|
-
...(mandatoryVideo || {}),
|
|
1804
|
-
},
|
|
1805
|
-
} : {
|
|
1806
|
-
cursor: 'always',
|
|
1807
|
-
} : this._getVideoConstraints(enableVideo),
|
|
1808
|
-
screen: screenSharing,
|
|
1809
|
-
desktop: isDesktop,
|
|
1810
|
-
conference,
|
|
1811
|
-
},
|
|
1812
|
-
enableVideo,
|
|
1813
|
-
conference,
|
|
1814
|
-
offerOptions: {
|
|
1815
|
-
OfferToReceiveAudio: this._hasAudio(),
|
|
1816
|
-
OfferToReceiveVideo: enableVideo,
|
|
1817
|
-
mandatory: {
|
|
1818
|
-
OfferToReceiveAudio: this._hasAudio(),
|
|
1819
|
-
OfferToReceiveVideo: enableVideo,
|
|
1820
|
-
},
|
|
1821
|
-
},
|
|
1822
|
-
};
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
1524
|
isConference(sessionId: string): boolean {
|
|
1826
1525
|
return sessionId in this.conferences;
|
|
1827
1526
|
}
|
|
@@ -1913,30 +1612,16 @@ export default class WebRTCClient extends Emitter {
|
|
|
1913
1612
|
}
|
|
1914
1613
|
}
|
|
1915
1614
|
|
|
1916
|
-
_isWeb(): boolean {
|
|
1917
|
-
return typeof window === 'object' && typeof document === 'object';
|
|
1918
|
-
}
|
|
1919
|
-
|
|
1920
|
-
_isVideoTrack(track: MediaStreamTrack): boolean {
|
|
1921
|
-
return track.kind === 'video' && track.readyState === 'live';
|
|
1922
|
-
}
|
|
1923
|
-
|
|
1924
1615
|
_hasAudio(): boolean {
|
|
1925
1616
|
return this.hasAudio;
|
|
1926
1617
|
}
|
|
1927
1618
|
|
|
1928
1619
|
_getAudioConstraints(): MediaTrackConstraints | boolean {
|
|
1929
|
-
|
|
1930
|
-
return this.audio?.deviceId?.exact ? this.audio : true;
|
|
1620
|
+
return getAudioConstraints(this.audio);
|
|
1931
1621
|
}
|
|
1932
1622
|
|
|
1933
1623
|
_getVideoConstraints(video = false): MediaTrackConstraints | boolean {
|
|
1934
|
-
|
|
1935
|
-
return false;
|
|
1936
|
-
}
|
|
1937
|
-
|
|
1938
|
-
// @ts-ignore: media constraints
|
|
1939
|
-
return this.video?.deviceId?.exact ? this.video : true;
|
|
1624
|
+
return getVideoConstraints(video && this.video);
|
|
1940
1625
|
}
|
|
1941
1626
|
|
|
1942
1627
|
_connectIfNeeded(): Promise<any> {
|
|
@@ -1999,101 +1684,9 @@ export default class WebRTCClient extends Emitter {
|
|
|
1999
1684
|
}));
|
|
2000
1685
|
}
|
|
2001
1686
|
|
|
2002
|
-
_createUaOptions(uaOptionsOverrides: UserAgentConfigOverrides = {}): UserAgentOptions {
|
|
2003
|
-
let {
|
|
2004
|
-
host,
|
|
2005
|
-
} = this.config;
|
|
2006
|
-
let port: number | string = this.config.port || 443;
|
|
2007
|
-
|
|
2008
|
-
if (this.config.websocketSip) {
|
|
2009
|
-
const webSocketSip = this.config.websocketSip.split(':');
|
|
2010
|
-
[host] = webSocketSip;
|
|
2011
|
-
port = Number(webSocketSip[1]);
|
|
2012
|
-
}
|
|
2013
|
-
const { userUuid } = this.config;
|
|
2014
|
-
|
|
2015
|
-
const uaOptions: UserAgentOptions = {
|
|
2016
|
-
noAnswerTimeout: NO_ANSWER_TIMEOUT,
|
|
2017
|
-
authorizationUsername: this.config.authorizationUser,
|
|
2018
|
-
authorizationPassword: this.config.password,
|
|
2019
|
-
displayName: this.config.displayName,
|
|
2020
|
-
hackIpInContact: true,
|
|
2021
|
-
contactParams: {
|
|
2022
|
-
transport: 'wss',
|
|
2023
|
-
},
|
|
2024
|
-
logBuiltinEnabled: this.config.log ? this.config.log.builtinEnabled : null,
|
|
2025
|
-
logLevel: this.config.log ? this.config.log.logLevel : null,
|
|
2026
|
-
logConnector: this.config.log ? this.config.log.connector : null,
|
|
2027
|
-
uri: this._makeURI(this.config.authorizationUser || ''),
|
|
2028
|
-
userAgentString: this.config.userAgentString || 'wazo-sdk',
|
|
2029
|
-
reconnectionAttempts: 10000,
|
|
2030
|
-
reconnectionDelay: 5,
|
|
2031
|
-
|
|
2032
|
-
sessionDescriptionHandlerFactory: (session: Session, options: SessionDescriptionHandlerFactoryOptions = {}) => {
|
|
2033
|
-
const uaLogger = session.userAgent.getLogger('sip.WazoSessionDescriptionHandler');
|
|
2034
|
-
|
|
2035
|
-
const isWeb = this._isWeb();
|
|
2036
|
-
|
|
2037
|
-
const iceGatheringTimeout = 'peerConnectionOptions' in options ? (options.peerConnectionOptions as any).iceGatheringTimeout || DEFAULT_ICE_TIMEOUT : DEFAULT_ICE_TIMEOUT;
|
|
2038
|
-
const sdhOptions: SessionDescriptionHandlerConfiguration = { ...options,
|
|
2039
|
-
iceGatheringTimeout,
|
|
2040
|
-
peerConnectionConfiguration: { ...defaultPeerConnectionConfiguration(),
|
|
2041
|
-
...(options.peerConnectionConfiguration || {}),
|
|
2042
|
-
},
|
|
2043
|
-
};
|
|
2044
|
-
return new WazoSessionDescriptionHandler(uaLogger, wazoMediaStreamFactory, sdhOptions, isWeb, session as WazoSession);
|
|
2045
|
-
},
|
|
2046
|
-
transportOptions: {
|
|
2047
|
-
traceSip: uaOptionsOverrides?.traceSip || false,
|
|
2048
|
-
wsServers: `wss://${host}:${port}/api/asterisk/ws${userUuid ? `?userUuid=${userUuid}` : ''}`,
|
|
2049
|
-
},
|
|
2050
|
-
sessionDescriptionHandlerFactoryOptions: {
|
|
2051
|
-
modifiers: [replaceLocalIpModifier],
|
|
2052
|
-
alwaysAcquireMediaFirst: this.isFirefox(),
|
|
2053
|
-
constraints: {
|
|
2054
|
-
audio: this._getAudioConstraints(),
|
|
2055
|
-
video: this._getVideoConstraints(),
|
|
2056
|
-
},
|
|
2057
|
-
peerConnectionOptions: {
|
|
2058
|
-
iceCheckingTimeout: this.config.iceCheckingTimeout || 1000,
|
|
2059
|
-
iceGatheringTimeout: this.config.iceCheckingTimeout || 1000,
|
|
2060
|
-
rtcConfiguration: {
|
|
2061
|
-
rtcpMuxPolicy: 'require',
|
|
2062
|
-
iceServers: WebRTCClient.getIceServers(this.config.host),
|
|
2063
|
-
...this._getRtcOptions(),
|
|
2064
|
-
...(uaOptionsOverrides?.peerConnectionOptions || {}),
|
|
2065
|
-
},
|
|
2066
|
-
},
|
|
2067
|
-
// Configuration used in SDH to create the PeerConnection
|
|
2068
|
-
peerConnectionConfiguration: {
|
|
2069
|
-
rtcpMuxPolicy: 'require',
|
|
2070
|
-
iceServers: WebRTCClient.getIceServers(this.config.host),
|
|
2071
|
-
...(uaOptionsOverrides?.peerConnectionOptions || {}),
|
|
2072
|
-
},
|
|
2073
|
-
},
|
|
2074
|
-
};
|
|
2075
|
-
|
|
2076
|
-
delete uaOptionsOverrides?.traceSip;
|
|
2077
|
-
|
|
2078
|
-
return {
|
|
2079
|
-
...uaOptions,
|
|
2080
|
-
...uaOptionsOverrides,
|
|
2081
|
-
};
|
|
2082
|
-
}
|
|
2083
|
-
|
|
2084
|
-
// eslint-disable-next-line no-unused-vars
|
|
2085
|
-
_getRtcOptions() {
|
|
2086
|
-
return {
|
|
2087
|
-
mandatory: {
|
|
2088
|
-
OfferToReceiveAudio: this._hasAudio(),
|
|
2089
|
-
OfferToReceiveVideo: false,
|
|
2090
|
-
},
|
|
2091
|
-
};
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
1687
|
// Invitation and Inviter extends Session
|
|
2095
1688
|
_setupSession(session: WazoSession): void {
|
|
2096
|
-
const sipSessionId =
|
|
1689
|
+
const sipSessionId = getSipSessionId(session);
|
|
2097
1690
|
|
|
2098
1691
|
// When receiving an Invitation, the delegate is not defined.
|
|
2099
1692
|
if (!session.delegate) {
|
|
@@ -2102,7 +1695,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
2102
1695
|
|
|
2103
1696
|
session.delegate.onSessionDescriptionHandler = (sdh: SessionDescriptionHandler & { on: (type: string, e: any) => void }) => {
|
|
2104
1697
|
sdh.on('error', (e: any) => {
|
|
2105
|
-
this.eventEmitter.emit(ON_ERROR, e);
|
|
1698
|
+
this.eventEmitter.emit(ON_ERROR, e, session);
|
|
2106
1699
|
});
|
|
2107
1700
|
sdh.peerConnectionDelegate = {
|
|
2108
1701
|
onicecandidateerror: (error: Record<string, any>) => {
|
|
@@ -2157,7 +1750,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
2157
1750
|
session.outgoingInviteRequest.message.body.body = request.body;
|
|
2158
1751
|
}
|
|
2159
1752
|
|
|
2160
|
-
this.
|
|
1753
|
+
updateRemoteStream(this.sipSessions[sipSessionId], false);
|
|
2161
1754
|
|
|
2162
1755
|
await this._setupMedias(session);
|
|
2163
1756
|
|
|
@@ -2168,8 +1761,8 @@ export default class WebRTCClient extends Emitter {
|
|
|
2168
1761
|
async _onProgress(session: WazoSession, early = false): Promise<void> {
|
|
2169
1762
|
await this._setupMedias(session);
|
|
2170
1763
|
|
|
2171
|
-
const sessionId =
|
|
2172
|
-
|
|
1764
|
+
const sessionId = getSipSessionId(session).substr(0, 20);
|
|
1765
|
+
updateRemoteStream(session);
|
|
2173
1766
|
logger.info('progress received', {
|
|
2174
1767
|
sessionId,
|
|
2175
1768
|
early,
|
|
@@ -2187,30 +1780,16 @@ export default class WebRTCClient extends Emitter {
|
|
|
2187
1780
|
|
|
2188
1781
|
await this._setupMedias(session);
|
|
2189
1782
|
|
|
2190
|
-
|
|
2191
|
-
const pc = (session
|
|
1783
|
+
updateRemoteStream(session, initAllTracks);
|
|
1784
|
+
const pc = getPeerConnection(session);
|
|
2192
1785
|
|
|
2193
1786
|
const onTrack = (event: RTCTrackEvent | MediaStreamTrackEvent) => {
|
|
2194
|
-
const
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
kind,
|
|
2198
|
-
label,
|
|
2199
|
-
readyState,
|
|
2200
|
-
id,
|
|
2201
|
-
muted,
|
|
2202
|
-
} = event.track;
|
|
2203
|
-
logger.info('on track event', {
|
|
2204
|
-
isAudioOnly,
|
|
2205
|
-
kind,
|
|
2206
|
-
label,
|
|
2207
|
-
readyState,
|
|
2208
|
-
id,
|
|
2209
|
-
muted,
|
|
2210
|
-
});
|
|
1787
|
+
const audioOnly = isAudioOnly(session);
|
|
1788
|
+
const { kind, label, readyState, id, muted } = event.track;
|
|
1789
|
+
logger.info('on track event', { audioOnly, kind, label, readyState, id, muted });
|
|
2211
1790
|
|
|
2212
1791
|
// Stop video track in audio only mode
|
|
2213
|
-
if (
|
|
1792
|
+
if (audioOnly && kind === 'video') {
|
|
2214
1793
|
event.track.stop();
|
|
2215
1794
|
}
|
|
2216
1795
|
|
|
@@ -2243,18 +1822,14 @@ export default class WebRTCClient extends Emitter {
|
|
|
2243
1822
|
this._startSendingStats(session);
|
|
2244
1823
|
}
|
|
2245
1824
|
|
|
2246
|
-
_isAudioOnly(session: WazoSession): boolean {
|
|
2247
|
-
return Boolean(session.sessionDescriptionHandlerModifiersReInvite.find(modifier => modifier === stripVideo));
|
|
2248
|
-
}
|
|
2249
|
-
|
|
2250
1825
|
async _setupMedias(session: WazoSession, newStream: MediaStream | null | undefined = null): Promise<void> {
|
|
2251
|
-
if (!
|
|
1826
|
+
if (!isWeb()) {
|
|
2252
1827
|
logger.info('Setup media on mobile, no need to setup html element, bailing');
|
|
2253
1828
|
return;
|
|
2254
1829
|
}
|
|
2255
1830
|
|
|
2256
1831
|
// Safari hack, because you cannot call .play() from a non user action
|
|
2257
|
-
const sessionId =
|
|
1832
|
+
const sessionId = getSipSessionId(session);
|
|
2258
1833
|
const isConference = this.isConference(sessionId);
|
|
2259
1834
|
|
|
2260
1835
|
if (sessionId in this.audioElements) {
|
|
@@ -2264,7 +1839,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
2264
1839
|
return;
|
|
2265
1840
|
}
|
|
2266
1841
|
|
|
2267
|
-
if (this._hasAudio() &&
|
|
1842
|
+
if (this._hasAudio() && isWeb() && !(sessionId in this.audioElements)) {
|
|
2268
1843
|
await this.createAudioElementFor(sessionId);
|
|
2269
1844
|
}
|
|
2270
1845
|
|
|
@@ -2309,11 +1884,11 @@ export default class WebRTCClient extends Emitter {
|
|
|
2309
1884
|
}
|
|
2310
1885
|
|
|
2311
1886
|
_cleanupMedia(session?: WazoSession): void {
|
|
2312
|
-
const sessionId =
|
|
1887
|
+
const sessionId = getSipSessionId(session);
|
|
2313
1888
|
const localStream = this.getLocalStream(sessionId);
|
|
2314
1889
|
|
|
2315
1890
|
if (localStream) {
|
|
2316
|
-
|
|
1891
|
+
cleanupStream(localStream);
|
|
2317
1892
|
}
|
|
2318
1893
|
|
|
2319
1894
|
const cleanLocalElement = (id: string) => {
|
|
@@ -2333,90 +1908,15 @@ export default class WebRTCClient extends Emitter {
|
|
|
2333
1908
|
delete this.audioElements[id];
|
|
2334
1909
|
};
|
|
2335
1910
|
|
|
2336
|
-
if (this._hasAudio() &&
|
|
1911
|
+
if (this._hasAudio() && isWeb()) {
|
|
2337
1912
|
if (session) {
|
|
2338
|
-
cleanLocalElement(
|
|
1913
|
+
cleanLocalElement(getSipSessionId(session));
|
|
2339
1914
|
} else {
|
|
2340
1915
|
Object.keys(this.audioElements).forEach(id => cleanLocalElement(id));
|
|
2341
1916
|
}
|
|
2342
1917
|
}
|
|
2343
1918
|
}
|
|
2344
1919
|
|
|
2345
|
-
_cleanupStream(stream: MediaStream): void {
|
|
2346
|
-
stream.getTracks().filter(track => track.enabled).forEach(track => track.stop());
|
|
2347
|
-
}
|
|
2348
|
-
|
|
2349
|
-
_toggleAudio(session: WazoSession, muteAudio: boolean): void {
|
|
2350
|
-
const sdh = session.sessionDescriptionHandler as SessionDescriptionHandler;
|
|
2351
|
-
const pc = (sdh?.peerConnection || null) as PeerConnection;
|
|
2352
|
-
|
|
2353
|
-
if (!pc) {
|
|
2354
|
-
return;
|
|
2355
|
-
}
|
|
2356
|
-
|
|
2357
|
-
if (pc.getSenders) {
|
|
2358
|
-
pc.getSenders().forEach((sender) => {
|
|
2359
|
-
if (sender && sender.track && sender.track.kind === 'audio') {
|
|
2360
|
-
// eslint-disable-next-line
|
|
2361
|
-
sender.track.enabled = !muteAudio;
|
|
2362
|
-
}
|
|
2363
|
-
});
|
|
2364
|
-
} else {
|
|
2365
|
-
pc.getLocalStreams().forEach((stream: MediaStream) => {
|
|
2366
|
-
stream.getAudioTracks().forEach(track => {
|
|
2367
|
-
// eslint-disable-next-line
|
|
2368
|
-
track.enabled = !muteAudio;
|
|
2369
|
-
});
|
|
2370
|
-
});
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
|
|
2374
|
-
_toggleVideo(session: WazoSession, muteCamera: boolean): void {
|
|
2375
|
-
const sdh = session.sessionDescriptionHandler as SessionDescriptionHandler;
|
|
2376
|
-
const pc = sdh?.peerConnection as PeerConnection;
|
|
2377
|
-
|
|
2378
|
-
if (pc?.getSenders) {
|
|
2379
|
-
pc.getSenders().forEach((sender) => {
|
|
2380
|
-
if (sender && sender.track && sender.track.kind === 'video') {
|
|
2381
|
-
// eslint-disable-next-line
|
|
2382
|
-
sender.track.enabled = !muteCamera;
|
|
2383
|
-
}
|
|
2384
|
-
});
|
|
2385
|
-
} else {
|
|
2386
|
-
pc.getLocalStreams().forEach((stream: MediaStream) => {
|
|
2387
|
-
stream.getVideoTracks().forEach(track => {
|
|
2388
|
-
// eslint-disable-next-line
|
|
2389
|
-
track.enabled = !muteCamera;
|
|
2390
|
-
});
|
|
2391
|
-
});
|
|
2392
|
-
}
|
|
2393
|
-
}
|
|
2394
|
-
|
|
2395
|
-
/**
|
|
2396
|
-
* @param pc RTCPeerConnection
|
|
2397
|
-
*/
|
|
2398
|
-
_getRemoteStream(pc: PeerConnection): MediaStream | null {
|
|
2399
|
-
let remoteStream: MediaStream | null = null;
|
|
2400
|
-
|
|
2401
|
-
if (pc && pc.getReceivers) {
|
|
2402
|
-
remoteStream = typeof global !== 'undefined' && global.window && global.window.MediaStream ? new global.window.MediaStream() : new window.MediaStream();
|
|
2403
|
-
|
|
2404
|
-
pc.getReceivers().forEach((receiver) => {
|
|
2405
|
-
const {
|
|
2406
|
-
track,
|
|
2407
|
-
} = receiver;
|
|
2408
|
-
|
|
2409
|
-
if (track && remoteStream) {
|
|
2410
|
-
remoteStream.addTrack(track);
|
|
2411
|
-
}
|
|
2412
|
-
});
|
|
2413
|
-
} else if (pc) {
|
|
2414
|
-
[remoteStream] = pc.getRemoteStreams();
|
|
2415
|
-
}
|
|
2416
|
-
|
|
2417
|
-
return remoteStream;
|
|
2418
|
-
}
|
|
2419
|
-
|
|
2420
1920
|
_cleanupRegister(): void {
|
|
2421
1921
|
if (this.registerer) {
|
|
2422
1922
|
// @ts-ignore: removeAllListeners does not exist
|
|
@@ -2433,7 +1933,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
2433
1933
|
return;
|
|
2434
1934
|
}
|
|
2435
1935
|
|
|
2436
|
-
const sessionId =
|
|
1936
|
+
const sessionId = getSipSessionId(session);
|
|
2437
1937
|
getStats(pc, (result: Record<string, any>) => {
|
|
2438
1938
|
const {
|
|
2439
1939
|
results,
|
|
@@ -2450,7 +1950,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
2450
1950
|
}
|
|
2451
1951
|
|
|
2452
1952
|
_stopSendingStats(session: WazoSession): void {
|
|
2453
|
-
const sessionId =
|
|
1953
|
+
const sessionId = getSipSessionId(session);
|
|
2454
1954
|
logger.trace('Check for stopping stats', {
|
|
2455
1955
|
sessionId,
|
|
2456
1956
|
ids: Object.keys(this.statsIntervals),
|
|
@@ -2465,10 +1965,6 @@ export default class WebRTCClient extends Emitter {
|
|
|
2465
1965
|
}
|
|
2466
1966
|
}
|
|
2467
1967
|
|
|
2468
|
-
_makeURI(target: string): URI | undefined {
|
|
2469
|
-
return UserAgent.makeURI(`sip:${target}@${this.config.host}`);
|
|
2470
|
-
}
|
|
2471
|
-
|
|
2472
1968
|
async _disconnectTransport(force = false) {
|
|
2473
1969
|
const transport = this.userAgent?.transport as WazoTransport;
|
|
2474
1970
|
|
|
@@ -2494,110 +1990,14 @@ export default class WebRTCClient extends Emitter {
|
|
|
2494
1990
|
}
|
|
2495
1991
|
|
|
2496
1992
|
async _fetchNetworkStats(sessionId: string): Promise<Record<string, any> | null | undefined> {
|
|
2497
|
-
const session = this.
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
if (!stats || !(sessionId in this.sessionNetworkStats)) {
|
|
1993
|
+
const session = this.sipSessions[sessionId];
|
|
1994
|
+
if (!(sessionId in this.sessionNetworkStats)) {
|
|
2501
1995
|
return Promise.resolve(null);
|
|
2502
1996
|
}
|
|
2503
1997
|
|
|
2504
|
-
const networkStats: Record<string, any> = {
|
|
2505
|
-
audioBytesSent: 0,
|
|
2506
|
-
videoBytesSent: 0,
|
|
2507
|
-
videoBytesReceived: 0,
|
|
2508
|
-
audioBytesReceived: 0,
|
|
2509
|
-
};
|
|
2510
1998
|
const nbStats = this.sessionNetworkStats[sessionId].length;
|
|
2511
1999
|
const lastNetworkStats = this.sessionNetworkStats[sessionId][nbStats - 1];
|
|
2512
|
-
const
|
|
2513
|
-
const lastAudioContentSent = lastNetworkStats ? lastNetworkStats.audioContentSent : 0;
|
|
2514
|
-
const lastVideoSent = lastNetworkStats ? lastNetworkStats.videoBytesSent : 0;
|
|
2515
|
-
const lastAudioReceived = lastNetworkStats ? lastNetworkStats.audioBytesReceived : 0;
|
|
2516
|
-
const lastAudioContentReceived = lastNetworkStats ? lastNetworkStats.audioContentReceived : 0;
|
|
2517
|
-
const lastVideoReceived = lastNetworkStats ? lastNetworkStats.videoBytesReceived : 0;
|
|
2518
|
-
const lastTransportSent = lastNetworkStats ? lastNetworkStats.transportSent : 0;
|
|
2519
|
-
const lastTransportReceived = lastNetworkStats ? lastNetworkStats.transportReceived : 0;
|
|
2520
|
-
let audioBytesSent = 0; // content + header
|
|
2521
|
-
|
|
2522
|
-
let audioBytesReceived = 0; // content + header
|
|
2523
|
-
|
|
2524
|
-
let audioContentSent = 0; // useful to detect blank call
|
|
2525
|
-
|
|
2526
|
-
let audioContentReceived = 0;
|
|
2527
|
-
let videoBytesSent = 0;
|
|
2528
|
-
let videoBytesReceived = 0;
|
|
2529
|
-
let transportSent = 0;
|
|
2530
|
-
let transportReceived = 0;
|
|
2531
|
-
let packetsLost = 0;
|
|
2532
|
-
stats.forEach(report => {
|
|
2533
|
-
if (report.type === 'outbound-rtp' && report.kind === 'audio') {
|
|
2534
|
-
audioBytesSent += Number(report.bytesSent) + Number(report.headerBytesSent);
|
|
2535
|
-
audioContentSent += Number(report.bytesSent);
|
|
2536
|
-
}
|
|
2537
|
-
|
|
2538
|
-
if (report.type === 'remote-outbound-rtp' && report.kind === 'audio') {
|
|
2539
|
-
audioBytesSent += Number(report.bytesSent);
|
|
2540
|
-
}
|
|
2541
|
-
|
|
2542
|
-
if (report.type === 'outbound-rtp' && report.kind === 'video') {
|
|
2543
|
-
videoBytesSent += Number(report.bytesSent) + Number(report.headerBytesSent);
|
|
2544
|
-
}
|
|
2545
|
-
|
|
2546
|
-
if (report.type === 'inbound-rtp' && report.kind === 'audio') {
|
|
2547
|
-
packetsLost += Number(report.packetsLost);
|
|
2548
|
-
networkStats.packetsReceived = Number(report.packetsReceived);
|
|
2549
|
-
audioBytesReceived += Number(report.bytesReceived) + Number(report.headerBytesReceived);
|
|
2550
|
-
audioContentReceived += Number(report.bytesReceived);
|
|
2551
|
-
}
|
|
2552
|
-
|
|
2553
|
-
if (report.type === 'inbound-rtp' && report.kind === 'video') {
|
|
2554
|
-
videoBytesReceived += Number(report.bytesReceived) + Number(report.headerBytesReceived);
|
|
2555
|
-
}
|
|
2556
|
-
|
|
2557
|
-
if (report.type === 'outbound-rtp' && report.kind === 'video') {
|
|
2558
|
-
if ('framesPerSecond' in report) {
|
|
2559
|
-
networkStats.framesPerSecond = Number(report.framesPerSecond);
|
|
2560
|
-
}
|
|
2561
|
-
|
|
2562
|
-
if ('framerateMean' in report) {
|
|
2563
|
-
// framerateMean is only available in FF
|
|
2564
|
-
networkStats.framesPerSecond = Math.round(report.framerateMean);
|
|
2565
|
-
}
|
|
2566
|
-
}
|
|
2567
|
-
|
|
2568
|
-
if (report.type === 'remote-inbound-rtp' && report.kind === 'audio') {
|
|
2569
|
-
packetsLost += Number(report.packetsLost);
|
|
2570
|
-
networkStats.roundTripTime = Number(report.roundTripTime);
|
|
2571
|
-
networkStats.jitter = Number(report.jitter);
|
|
2572
|
-
}
|
|
2573
|
-
|
|
2574
|
-
if (report.type === 'remote-inbound-rtp' && report.kind === 'video') {
|
|
2575
|
-
packetsLost += Number(report.packetsLost);
|
|
2576
|
-
}
|
|
2577
|
-
|
|
2578
|
-
if (report.type === 'transport') {
|
|
2579
|
-
transportSent += Number(report.bytesSent);
|
|
2580
|
-
transportReceived += Number(report.bytesReceived);
|
|
2581
|
-
}
|
|
2582
|
-
});
|
|
2583
|
-
networkStats.packetsLost = packetsLost;
|
|
2584
|
-
networkStats.totalAudioBytesSent = audioBytesSent;
|
|
2585
|
-
networkStats.audioBytesSent = audioBytesSent - lastAudioSent;
|
|
2586
|
-
networkStats.totalAudioBytesReceived = audioBytesReceived - lastAudioReceived;
|
|
2587
|
-
networkStats.audioBytesReceived = audioBytesReceived;
|
|
2588
|
-
networkStats.totalAudioContentSent = audioContentSent;
|
|
2589
|
-
networkStats.audioContentSent = audioContentSent - lastAudioContentSent;
|
|
2590
|
-
networkStats.totalAudioBytesReceived = audioContentReceived - lastAudioContentReceived;
|
|
2591
|
-
networkStats.audioContentReceived = audioContentReceived;
|
|
2592
|
-
networkStats.totalVideoBytesSent = videoBytesSent;
|
|
2593
|
-
networkStats.videoBytesSent = videoBytesSent - lastVideoSent;
|
|
2594
|
-
networkStats.totalVideoBytesReceived = videoBytesReceived;
|
|
2595
|
-
networkStats.videoBytesReceived = videoBytesReceived - lastVideoReceived;
|
|
2596
|
-
networkStats.totalTransportSent = transportSent - lastTransportSent;
|
|
2597
|
-
networkStats.transportSent = transportSent;
|
|
2598
|
-
networkStats.totalTransportReceived = transportReceived;
|
|
2599
|
-
networkStats.transportReceived = transportReceived - lastTransportReceived;
|
|
2600
|
-
networkStats.bandwidth = networkStats.audioBytesSent + networkStats.audioBytesReceived + networkStats.videoBytesSent + networkStats.videoBytesReceived + networkStats.transportReceived + networkStats.transportSent;
|
|
2000
|
+
const networkStats = await fetchNetworkStats(session, lastNetworkStats);
|
|
2601
2001
|
|
|
2602
2002
|
if (this.sessionNetworkStats[sessionId]) {
|
|
2603
2003
|
this.eventEmitter.emit(ON_NETWORK_STATS, session, networkStats, this.sessionNetworkStats[sessionId]);
|