@wazo/sdk 0.43.6 → 1.0.0-alpha.1
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 +2 -2
- package/esm/__tests__/__voice__/call.test.js +72 -0
- package/esm/__tests__/__voice__/softphone.test.js +66 -0
- 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/index.js +2 -0
- package/esm/simple/Phone.js +2 -1
- 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/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/index.d.ts +2 -0
- package/esm/types/index.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/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/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/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/index.js +6 -2
- package/lib/simple/Phone.js +2 -1
- 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/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/index.d.ts +2 -0
- package/lib/types/index.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/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/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/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/index.ts +2 -0
- package/src/simple/Phone.ts +2 -1
- 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/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/esm/web-rtc-client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable class-methods-use-this, no-param-reassign, max-classes-per-file, no-underscore-dangle */
|
|
2
|
-
/* global
|
|
2
|
+
/* global document, navigator */
|
|
3
3
|
import 'webrtc-adapter';
|
|
4
4
|
import { UserAgentState } from 'sip.js/lib/api/user-agent-state';
|
|
5
5
|
import { Parser } from 'sip.js/lib/core/messages/parser';
|
|
@@ -11,23 +11,14 @@ import { Messager } from 'sip.js/lib/api/messager';
|
|
|
11
11
|
import { RegistererState } from 'sip.js/lib/api/registerer-state';
|
|
12
12
|
import { SessionState } from 'sip.js/lib/api/session-state';
|
|
13
13
|
import { TransportState } from 'sip.js/lib/api/transport-state';
|
|
14
|
-
import { defaultPeerConnectionConfiguration } from 'sip.js/lib/platform/web/session-description-handler/peer-connection-configuration-default';
|
|
15
14
|
import getStats from 'getstats';
|
|
16
15
|
import { Inviter, Invitation, Registerer } from 'sip.js/lib/api';
|
|
17
|
-
import WazoSessionDescriptionHandler, { wazoMediaStreamFactory } from './lib/WazoSessionDescriptionHandler';
|
|
18
16
|
import Emitter from './utils/Emitter';
|
|
19
17
|
import ApiClient from './api-client';
|
|
20
18
|
import IssueReporter from './service/IssueReporter';
|
|
21
19
|
import Heartbeat from './utils/Heartbeat';
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
// We need to replace 0.0.0.0 to 127.0.0.1 in the sdp to avoid MOH during a createOffer.
|
|
25
|
-
export const replaceLocalIpModifier = (description) => Promise.resolve({
|
|
26
|
-
...JSON.parse(JSON.stringify(description)),
|
|
27
|
-
sdp: description.sdp.replace('c=IN IP4 0.0.0.0', 'c=IN IP4 127.0.0.1'),
|
|
28
|
-
});
|
|
29
|
-
const SIP_ID_LENGTH = 36;
|
|
30
|
-
const DEFAULT_ICE_TIMEOUT = 3000;
|
|
20
|
+
import { getSipSessionId, makeURI, replaceLocalIpModifier, SIP_ID_LENGTH } from './utils/sdp';
|
|
21
|
+
import { cleanupStream, createUaOptions, fetchNetworkStats, getAudioConstraints, getLocalStream, getLocalTracks, getLocalVideoStream, getMediaConfiguration, getPeerConnection, getRemoteStream, getRemoteTracks, getRemoteVideoStream, getRemoteVideoStreamFromPc, getVideoConstraints, hasALocalVideoTrack, hasARemoteVideoTrack, hasAVideoTrack, hasLocalVideo, hasRemoteVideo, hasVideoTrack, isAudioOnly, isVideoRemotelyHeld, isWeb, sessionWantsToDoVideo, setLocalMediaStream, toggleAudio, toggleVideo, updateRemoteStream } from './utils/webrtc';
|
|
31
22
|
const SEND_STATS_DELAY = 5000;
|
|
32
23
|
const states = ['STATUS_NULL', 'STATUS_NEW', 'STATUS_CONNECTING', 'STATUS_CONNECTED', 'STATUS_COMPLETED'];
|
|
33
24
|
const logger = IssueReporter ? IssueReporter.loggerFor('webrtc-client') : console;
|
|
@@ -56,8 +47,6 @@ export const transportEvents = [CONNECTED, DISCONNECTED, TRANSPORT_ERROR, MESSAG
|
|
|
56
47
|
export class CanceledCallError extends Error {
|
|
57
48
|
}
|
|
58
49
|
const MAX_REGISTER_TRIES = 5;
|
|
59
|
-
// setting a 24hr timeout and letting the backend define the actual value
|
|
60
|
-
const NO_ANSWER_TIMEOUT = 60 * 60 * 24; // in seconds
|
|
61
50
|
export default class WebRTCClient extends Emitter {
|
|
62
51
|
clientId;
|
|
63
52
|
config;
|
|
@@ -104,18 +93,6 @@ export default class WebRTCClient extends Emitter {
|
|
|
104
93
|
ON_EARLY_MEDIA;
|
|
105
94
|
ON_PROGRESS;
|
|
106
95
|
ON_DISCONNECTED;
|
|
107
|
-
static isAPrivateIp(ip) {
|
|
108
|
-
const regex = /^(?:10|127|172\.(?:1[6-9]|2[0-9]|3[01])|192\.168)\..*/;
|
|
109
|
-
return regex.exec(ip) == null;
|
|
110
|
-
}
|
|
111
|
-
static getIceServers(ip) {
|
|
112
|
-
if (WebRTCClient.isAPrivateIp(ip)) {
|
|
113
|
-
return [{
|
|
114
|
-
urls: ['stun:stun.l.google.com:19302', 'stun:stun4.l.google.com:19302'],
|
|
115
|
-
}];
|
|
116
|
-
}
|
|
117
|
-
return [];
|
|
118
|
-
}
|
|
119
96
|
constructor(config, session, uaConfigOverrides) {
|
|
120
97
|
super();
|
|
121
98
|
// For debug purpose
|
|
@@ -179,7 +156,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
179
156
|
this.audio = media.audio;
|
|
180
157
|
}
|
|
181
158
|
createUserAgent(uaConfigOverrides) {
|
|
182
|
-
const uaOptions = this.
|
|
159
|
+
const uaOptions = createUaOptions(this.config, uaConfigOverrides, this.audio, this.video);
|
|
183
160
|
logger.info('sdk webrtc, creating UA', {
|
|
184
161
|
uaOptions: { ...uaOptions, authorizationPassword: `${uaOptions?.authorizationPassword?.slice(0, 5)}xxxx` },
|
|
185
162
|
clientId: this.clientId,
|
|
@@ -196,7 +173,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
196
173
|
});
|
|
197
174
|
this._setupSession(invitation);
|
|
198
175
|
const shouldAutoAnswer = !!invitation.request.getHeader('alert-info');
|
|
199
|
-
this.eventEmitter.emit(INVITE, invitation,
|
|
176
|
+
this.eventEmitter.emit(INVITE, invitation, sessionWantsToDoVideo(invitation), shouldAutoAnswer);
|
|
200
177
|
},
|
|
201
178
|
};
|
|
202
179
|
const ua = new UserAgent(uaOptions);
|
|
@@ -287,7 +264,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
287
264
|
logger.info('sdk webrtc registering aborted due to a registration in progress.', { clientId: this.clientId });
|
|
288
265
|
return Promise.resolve();
|
|
289
266
|
}
|
|
290
|
-
const registerOptions =
|
|
267
|
+
const registerOptions = isWeb() ? {} : {
|
|
291
268
|
extraContactHeaderParams: ['mobility=mobile'],
|
|
292
269
|
};
|
|
293
270
|
const onRegisterFailed = () => {
|
|
@@ -445,7 +422,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
445
422
|
if (audioOnly) {
|
|
446
423
|
inviterOptions.sessionDescriptionHandlerModifiersReInvite = [stripVideo];
|
|
447
424
|
}
|
|
448
|
-
const uri = this.
|
|
425
|
+
const uri = makeURI(number, this.config.host);
|
|
449
426
|
let session = null;
|
|
450
427
|
if (uri) {
|
|
451
428
|
session = this.userAgent ? new Inviter(this.userAgent, uri, inviterOptions) : null;
|
|
@@ -458,7 +435,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
458
435
|
this._setupSession(session);
|
|
459
436
|
}
|
|
460
437
|
if (conference) {
|
|
461
|
-
this.conferences[
|
|
438
|
+
this.conferences[getSipSessionId(session)] = true;
|
|
462
439
|
}
|
|
463
440
|
const inviteOptions = {
|
|
464
441
|
requestDelegate: {
|
|
@@ -482,7 +459,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
482
459
|
this.eventEmitter.emit(REJECTED, session, response);
|
|
483
460
|
},
|
|
484
461
|
},
|
|
485
|
-
sessionDescriptionHandlerOptions:
|
|
462
|
+
sessionDescriptionHandlerOptions: getMediaConfiguration(enableVideo || false, conference),
|
|
486
463
|
};
|
|
487
464
|
if (inviteOptions.sessionDescriptionHandlerOptions) {
|
|
488
465
|
inviteOptions.sessionDescriptionHandlerOptions.audioOnly = audioOnly;
|
|
@@ -512,7 +489,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
512
489
|
return Promise.reject(new Error(error));
|
|
513
490
|
}
|
|
514
491
|
const options = {
|
|
515
|
-
sessionDescriptionHandlerOptions:
|
|
492
|
+
sessionDescriptionHandlerOptions: getMediaConfiguration(enableVideo || false),
|
|
516
493
|
};
|
|
517
494
|
return this._accept(session, options).then(() => {
|
|
518
495
|
// @ts-ignore: private
|
|
@@ -537,7 +514,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
537
514
|
try {
|
|
538
515
|
this._stopSendingStats(session);
|
|
539
516
|
this._cleanupMedia(session);
|
|
540
|
-
delete this.sipSessions[
|
|
517
|
+
delete this.sipSessions[getSipSessionId(session)];
|
|
541
518
|
// Check if Invitation or Inviter (Invitation = incoming call)
|
|
542
519
|
const isInviter = session instanceof Inviter;
|
|
543
520
|
// @see github.com/onsip/SIP.js/blob/f11dfd584bc9788ccfc94e03034020672b738975/src/platform/web/simple-user/simple-user.ts#L1004
|
|
@@ -557,16 +534,9 @@ export default class WebRTCClient extends Emitter {
|
|
|
557
534
|
}
|
|
558
535
|
return Promise.resolve(null);
|
|
559
536
|
}
|
|
560
|
-
async getStats(session) {
|
|
561
|
-
const pc = session.sessionDescriptionHandler?.peerConnection;
|
|
562
|
-
if (!pc) {
|
|
563
|
-
return null;
|
|
564
|
-
}
|
|
565
|
-
return pc.getStats(null);
|
|
566
|
-
}
|
|
567
537
|
// Fetch and emit an event at `interval` with session network stats
|
|
568
538
|
startNetworkMonitoring(session, interval = 1000) {
|
|
569
|
-
const sessionId =
|
|
539
|
+
const sessionId = getSipSessionId(session);
|
|
570
540
|
logger.info('starting network inspection', {
|
|
571
541
|
id: sessionId,
|
|
572
542
|
clientId: this.clientId,
|
|
@@ -575,7 +545,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
575
545
|
this.networkMonitoringInterval[sessionId] = setInterval(() => this._fetchNetworkStats(sessionId), interval);
|
|
576
546
|
}
|
|
577
547
|
stopNetworkMonitoring(session) {
|
|
578
|
-
const sessionId =
|
|
548
|
+
const sessionId = getSipSessionId(session);
|
|
579
549
|
const exists = (sessionId in this.networkMonitoringInterval);
|
|
580
550
|
logger.info('stopping network inspection', {
|
|
581
551
|
clientId: this.clientId,
|
|
@@ -643,24 +613,17 @@ export default class WebRTCClient extends Emitter {
|
|
|
643
613
|
this.userAgent = null;
|
|
644
614
|
logger.info('sdk webrtc client closed', { clientId: this.clientId });
|
|
645
615
|
}
|
|
646
|
-
getNumber(session) {
|
|
647
|
-
if (!session) {
|
|
648
|
-
return null;
|
|
649
|
-
}
|
|
650
|
-
// @ts-ignore: private
|
|
651
|
-
return session.remoteIdentity.uri._normal.user;
|
|
652
|
-
}
|
|
653
616
|
mute(session) {
|
|
654
617
|
logger.info('sdk webrtc mute', {
|
|
655
618
|
id: session.id,
|
|
656
619
|
});
|
|
657
|
-
|
|
620
|
+
toggleAudio(session, true);
|
|
658
621
|
}
|
|
659
622
|
unmute(session) {
|
|
660
623
|
logger.info('sdk webrtc unmute', {
|
|
661
624
|
id: session.id,
|
|
662
625
|
});
|
|
663
|
-
|
|
626
|
+
toggleAudio(session, false);
|
|
664
627
|
}
|
|
665
628
|
isAudioMuted(session) {
|
|
666
629
|
if (!session || !session.sessionDescriptionHandler) {
|
|
@@ -697,16 +660,16 @@ export default class WebRTCClient extends Emitter {
|
|
|
697
660
|
logger.info('sdk webrtc toggle camera on', {
|
|
698
661
|
id: session.id,
|
|
699
662
|
});
|
|
700
|
-
|
|
663
|
+
toggleVideo(session, false);
|
|
701
664
|
}
|
|
702
665
|
toggleCameraOff(session) {
|
|
703
666
|
logger.info('sdk webrtc toggle camera off', {
|
|
704
667
|
id: session.id,
|
|
705
668
|
});
|
|
706
|
-
|
|
669
|
+
toggleVideo(session, true);
|
|
707
670
|
}
|
|
708
671
|
hold(session, isConference = false, hadVideo = false) {
|
|
709
|
-
const sessionId =
|
|
672
|
+
const sessionId = getSipSessionId(session);
|
|
710
673
|
const hasVideo = hadVideo || this.hasLocalVideo(sessionId);
|
|
711
674
|
logger.info('sdk webrtc hold', {
|
|
712
675
|
sessionId,
|
|
@@ -734,8 +697,8 @@ export default class WebRTCClient extends Emitter {
|
|
|
734
697
|
hold: true,
|
|
735
698
|
conference: isConference,
|
|
736
699
|
};
|
|
737
|
-
const options =
|
|
738
|
-
if (!
|
|
700
|
+
const options = getMediaConfiguration(false, isConference);
|
|
701
|
+
if (!isWeb()) {
|
|
739
702
|
options.sessionDescriptionHandlerModifiers = [holdModifier];
|
|
740
703
|
}
|
|
741
704
|
options.sessionDescriptionHandlerOptions = {
|
|
@@ -754,7 +717,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
754
717
|
});
|
|
755
718
|
}
|
|
756
719
|
unhold(session, isConference = false) {
|
|
757
|
-
const sessionId =
|
|
720
|
+
const sessionId = getSipSessionId(session);
|
|
758
721
|
const hasVideo = sessionId in this.heldSessions && this.heldSessions[sessionId].hasVideo;
|
|
759
722
|
logger.info('sdk webrtc unhold', {
|
|
760
723
|
sessionId,
|
|
@@ -769,13 +732,13 @@ export default class WebRTCClient extends Emitter {
|
|
|
769
732
|
return Promise.resolve();
|
|
770
733
|
}
|
|
771
734
|
this.unmute(session);
|
|
772
|
-
delete this.heldSessions[
|
|
735
|
+
delete this.heldSessions[getSipSessionId(session)];
|
|
773
736
|
session.sessionDescriptionHandlerOptionsReInvite = {
|
|
774
737
|
hold: false,
|
|
775
738
|
conference: isConference,
|
|
776
739
|
};
|
|
777
|
-
const options =
|
|
778
|
-
if (!
|
|
740
|
+
const options = getMediaConfiguration(false, isConference);
|
|
741
|
+
if (!isWeb()) {
|
|
779
742
|
// We should sent an empty `sessionDescriptionHandlerModifiers` or sip.js will take the last sent modifiers
|
|
780
743
|
// (eg: holdModifier)
|
|
781
744
|
options.sessionDescriptionHandlerModifiers = [];
|
|
@@ -790,105 +753,27 @@ export default class WebRTCClient extends Emitter {
|
|
|
790
753
|
logger.warn('sdk webrtc re-invite during resume, error', e);
|
|
791
754
|
});
|
|
792
755
|
}
|
|
793
|
-
// Returns true if a re-INVITE is required
|
|
794
|
-
async upgradeToVideo(session, constraints, isConference) {
|
|
795
|
-
const pc = session.sessionDescriptionHandler?.peerConnection;
|
|
796
|
-
// Check if a video sender already exists
|
|
797
|
-
let videoSender;
|
|
798
|
-
if (isConference) {
|
|
799
|
-
// We search for the last transceiver without `video-` in the mid (video- means remote transceiver)
|
|
800
|
-
const transceivers = pc?.getTransceivers() || [];
|
|
801
|
-
const transceiverIdx = lastIndexOf(transceivers, transceiver => transceiver.sender.track === null && transceiver.mid && transceiver.mid.indexOf('video') === -1);
|
|
802
|
-
videoSender = transceiverIdx !== -1 ? transceivers[transceiverIdx].sender : null;
|
|
803
|
-
}
|
|
804
|
-
else {
|
|
805
|
-
videoSender = pc && pc.getSenders && pc.getSenders().find((sender) => sender.track === null);
|
|
806
|
-
}
|
|
807
|
-
if (!videoSender) {
|
|
808
|
-
// When no video sender found, it means that we're in the first video upgrade in 1:1
|
|
809
|
-
return;
|
|
810
|
-
}
|
|
811
|
-
// Reuse bidirectional video stream
|
|
812
|
-
const newStream = await this.getStreamFromConstraints(constraints);
|
|
813
|
-
if (!newStream) {
|
|
814
|
-
console.warn(`Can't create media stream with: ${JSON.stringify(constraints || {})}`);
|
|
815
|
-
return;
|
|
816
|
-
}
|
|
817
|
-
// Add previous local audio track
|
|
818
|
-
if (constraints && !constraints.audio) {
|
|
819
|
-
const localVideoStream = session.sessionDescriptionHandler?.localMediaStream;
|
|
820
|
-
const localAudioTrack = localVideoStream.getTracks().find(track => track.kind === 'audio');
|
|
821
|
-
if (localAudioTrack) {
|
|
822
|
-
newStream.addTrack(localAudioTrack);
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
const videoTrack = newStream.getVideoTracks()[0];
|
|
826
|
-
if (videoTrack) {
|
|
827
|
-
videoSender.replaceTrack(videoTrack);
|
|
828
|
-
}
|
|
829
|
-
this.setLocalMediaStream(this.getSipSessionId(session), newStream);
|
|
830
|
-
return newStream;
|
|
831
|
-
}
|
|
832
|
-
downgradeToAudio(session) {
|
|
833
|
-
// Release local video stream when downgrading to audio
|
|
834
|
-
const sessionDescriptionHandler = session.sessionDescriptionHandler;
|
|
835
|
-
const localStream = sessionDescriptionHandler?.localMediaStream;
|
|
836
|
-
const pc = sessionDescriptionHandler?.peerConnection;
|
|
837
|
-
const videoTracks = localStream.getVideoTracks();
|
|
838
|
-
// Remove video senders
|
|
839
|
-
if (pc?.getSenders) {
|
|
840
|
-
pc.getSenders().filter((sender) => sender.track && sender.track.kind === 'video').forEach((videoSender) => {
|
|
841
|
-
videoSender.replaceTrack(null);
|
|
842
|
-
});
|
|
843
|
-
}
|
|
844
|
-
videoTracks.forEach((videoTrack) => {
|
|
845
|
-
videoTrack.enabled = false;
|
|
846
|
-
videoTrack.stop();
|
|
847
|
-
localStream.removeTrack(videoTrack);
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
async getStreamFromConstraints(constraints, conference = false) {
|
|
851
|
-
const video = constraints && constraints.video;
|
|
852
|
-
const { constraints: newConstraints, } = this.getMediaConfiguration(video, conference, constraints);
|
|
853
|
-
let newStream = null;
|
|
854
|
-
try {
|
|
855
|
-
newStream = await wazoMediaStreamFactory(newConstraints);
|
|
856
|
-
}
|
|
857
|
-
catch (e) { // Nothing to do when the user cancel the screensharing prompt
|
|
858
|
-
}
|
|
859
|
-
if (!newStream) {
|
|
860
|
-
return null;
|
|
861
|
-
}
|
|
862
|
-
newStream.local = true;
|
|
863
|
-
return newStream;
|
|
864
|
-
}
|
|
865
756
|
getHeldSession(sessionId) {
|
|
866
757
|
return this.heldSessions[sessionId];
|
|
867
758
|
}
|
|
868
759
|
isCallHeld(session) {
|
|
869
|
-
return
|
|
760
|
+
return getSipSessionId(session) in this.heldSessions;
|
|
870
761
|
}
|
|
871
762
|
isVideoRemotelyHeld(sessionId) {
|
|
872
|
-
|
|
873
|
-
const sdp = pc && pc.remoteDescription ? pc.remoteDescription.sdp : null;
|
|
874
|
-
if (!sdp) {
|
|
875
|
-
return false;
|
|
876
|
-
}
|
|
877
|
-
const videoDirection = getVideoDirection(sdp);
|
|
878
|
-
return videoDirection === 'sendonly';
|
|
763
|
+
return isVideoRemotelyHeld(this.heldSessions[sessionId]);
|
|
879
764
|
}
|
|
880
765
|
sendDTMF(session, tone) {
|
|
881
766
|
if (!session.sessionDescriptionHandler) {
|
|
882
767
|
return false;
|
|
883
768
|
}
|
|
884
769
|
logger.info('Sending DTMF', {
|
|
885
|
-
id:
|
|
770
|
+
id: getSipSessionId(session),
|
|
886
771
|
tone,
|
|
887
772
|
});
|
|
888
773
|
return session.sessionDescriptionHandler.sendDtmf(tone);
|
|
889
774
|
}
|
|
890
775
|
message(destination, message) {
|
|
891
|
-
const uri = this.
|
|
776
|
+
const uri = makeURI(destination, this.config.host);
|
|
892
777
|
if (!this.userAgent || !uri) {
|
|
893
778
|
logger.warn('Null value on message', { uri, userAgent: this.userAgent });
|
|
894
779
|
return;
|
|
@@ -899,7 +784,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
899
784
|
transfer(session, target) {
|
|
900
785
|
this.hold(session);
|
|
901
786
|
logger.info('Transfering a session', {
|
|
902
|
-
id:
|
|
787
|
+
id: getSipSessionId(session),
|
|
903
788
|
target,
|
|
904
789
|
});
|
|
905
790
|
const options = {
|
|
@@ -910,7 +795,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
910
795
|
},
|
|
911
796
|
};
|
|
912
797
|
setTimeout(() => {
|
|
913
|
-
const uri = this.
|
|
798
|
+
const uri = makeURI(target, this.config.host);
|
|
914
799
|
if (!uri) {
|
|
915
800
|
logger.warn('transfer timeout: null URI');
|
|
916
801
|
return;
|
|
@@ -922,21 +807,21 @@ export default class WebRTCClient extends Emitter {
|
|
|
922
807
|
atxfer(session) {
|
|
923
808
|
this.hold(session);
|
|
924
809
|
logger.info('webrtc transfer started', {
|
|
925
|
-
id:
|
|
810
|
+
id: getSipSessionId(session),
|
|
926
811
|
});
|
|
927
812
|
const result = {
|
|
928
813
|
newSession: null,
|
|
929
814
|
init: async (target) => {
|
|
930
815
|
logger.info('webrtc transfer initialized', {
|
|
931
|
-
id:
|
|
816
|
+
id: getSipSessionId(session),
|
|
932
817
|
target,
|
|
933
818
|
});
|
|
934
819
|
result.newSession = await this.call(target);
|
|
935
820
|
},
|
|
936
821
|
complete: () => {
|
|
937
822
|
logger.info('webrtc transfer completed', {
|
|
938
|
-
id:
|
|
939
|
-
referId:
|
|
823
|
+
id: getSipSessionId(session),
|
|
824
|
+
referId: getSipSessionId(result.newSession),
|
|
940
825
|
});
|
|
941
826
|
session.refer(result.newSession);
|
|
942
827
|
},
|
|
@@ -977,7 +862,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
977
862
|
return;
|
|
978
863
|
}
|
|
979
864
|
const core = this.userAgent?.userAgentCore;
|
|
980
|
-
const fromURI =
|
|
865
|
+
const fromURI = makeURI(this.config.authorizationUser || '', this.config.host);
|
|
981
866
|
const toURI = new URI('sip', '', this.config.host);
|
|
982
867
|
if (fromURI) {
|
|
983
868
|
const message = core?.makeOutgoingRequestMessage('OPTIONS', toURI, fromURI, toURI, {});
|
|
@@ -995,9 +880,6 @@ export default class WebRTCClient extends Emitter {
|
|
|
995
880
|
getContactIdentifier() {
|
|
996
881
|
return this.userAgent ? `${this.userAgent.configuration.contactName}/${this.userAgent.contact.uri}` : null;
|
|
997
882
|
}
|
|
998
|
-
isFirefox() {
|
|
999
|
-
return this._isWeb() && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
|
1000
|
-
}
|
|
1001
883
|
changeAudioOutputVolume(volume) {
|
|
1002
884
|
logger.info('Changing audio output volume', {
|
|
1003
885
|
volume,
|
|
@@ -1101,12 +983,12 @@ export default class WebRTCClient extends Emitter {
|
|
|
1101
983
|
};
|
|
1102
984
|
}
|
|
1103
985
|
changeSessionVideoInputDevice(id, session) {
|
|
1104
|
-
if (!
|
|
986
|
+
if (!sessionWantsToDoVideo(session)) {
|
|
1105
987
|
return Promise.resolve();
|
|
1106
988
|
}
|
|
1107
989
|
const sdh = session.sessionDescriptionHandler;
|
|
1108
990
|
const pc = sdh.peerConnection;
|
|
1109
|
-
const sessionId =
|
|
991
|
+
const sessionId = getSipSessionId(session);
|
|
1110
992
|
const localStream = this.getLocalStream(sessionId);
|
|
1111
993
|
logger.info('changing video input device', {
|
|
1112
994
|
id,
|
|
@@ -1143,7 +1025,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
1143
1025
|
}
|
|
1144
1026
|
// let's update the local stream
|
|
1145
1027
|
this.eventEmitter.emit('onVideoInputChange', stream);
|
|
1146
|
-
this.
|
|
1028
|
+
setLocalMediaStream(this.sipSessions[sessionId], stream);
|
|
1147
1029
|
return stream;
|
|
1148
1030
|
});
|
|
1149
1031
|
}
|
|
@@ -1162,12 +1044,12 @@ export default class WebRTCClient extends Emitter {
|
|
|
1162
1044
|
return Promise.resolve();
|
|
1163
1045
|
}
|
|
1164
1046
|
const wasMuted = this.isAudioMuted(sipSession);
|
|
1165
|
-
const shouldDoVideo = newConstraints ? newConstraints.video :
|
|
1047
|
+
const shouldDoVideo = newConstraints ? newConstraints.video : sessionWantsToDoVideo(sipSession);
|
|
1166
1048
|
const shouldDoScreenSharing = newConstraints && newConstraints.screen;
|
|
1167
1049
|
const desktop = newConstraints && newConstraints.desktop;
|
|
1168
1050
|
logger.info('Sending reinvite', {
|
|
1169
1051
|
clientId: this.clientId,
|
|
1170
|
-
id:
|
|
1052
|
+
id: getSipSessionId(sipSession),
|
|
1171
1053
|
newConstraints,
|
|
1172
1054
|
conference,
|
|
1173
1055
|
audioOnly,
|
|
@@ -1185,7 +1067,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
1185
1067
|
conference,
|
|
1186
1068
|
audioOnly,
|
|
1187
1069
|
};
|
|
1188
|
-
const { constraints, } =
|
|
1070
|
+
const { constraints, } = getMediaConfiguration(shouldDoVideo, conference, newConstraints);
|
|
1189
1071
|
return sipSession.invite({
|
|
1190
1072
|
requestDelegate: {
|
|
1191
1073
|
onAccept: (response) => {
|
|
@@ -1201,11 +1083,11 @@ export default class WebRTCClient extends Emitter {
|
|
|
1201
1083
|
sipSession.incomingInviteRequest.message.body = response.message.body;
|
|
1202
1084
|
}
|
|
1203
1085
|
logger.info('on re-INVITE accepted', {
|
|
1204
|
-
id:
|
|
1086
|
+
id: getSipSessionId(sipSession),
|
|
1205
1087
|
wasMuted,
|
|
1206
1088
|
shouldDoScreenSharing,
|
|
1207
1089
|
});
|
|
1208
|
-
|
|
1090
|
+
updateRemoteStream(sipSession, false);
|
|
1209
1091
|
if (wasMuted) {
|
|
1210
1092
|
this.mute(sipSession);
|
|
1211
1093
|
}
|
|
@@ -1238,103 +1120,55 @@ export default class WebRTCClient extends Emitter {
|
|
|
1238
1120
|
return navigator.mediaDevices.getUserMedia(newConstraints);
|
|
1239
1121
|
}
|
|
1240
1122
|
getPeerConnection(sessionId) {
|
|
1241
|
-
|
|
1242
|
-
if (!sipSession) {
|
|
1243
|
-
return null;
|
|
1244
|
-
}
|
|
1245
|
-
return sipSession.sessionDescriptionHandler ? sipSession.sessionDescriptionHandler.peerConnection : null;
|
|
1123
|
+
return getPeerConnection(this.sipSessions[sessionId]);
|
|
1246
1124
|
}
|
|
1247
1125
|
// Local streams
|
|
1248
1126
|
getLocalStream(sessionId) {
|
|
1249
|
-
|
|
1250
|
-
return sipSession?.sessionDescriptionHandler?.localMediaStream || null;
|
|
1127
|
+
return getLocalStream(this.sipSessions[sessionId]);
|
|
1251
1128
|
}
|
|
1252
1129
|
getLocalTracks(sessionId) {
|
|
1253
|
-
|
|
1254
|
-
if (!localStream) {
|
|
1255
|
-
return [];
|
|
1256
|
-
}
|
|
1257
|
-
return localStream.getTracks();
|
|
1130
|
+
return getLocalTracks(this.sipSessions[sessionId]);
|
|
1258
1131
|
}
|
|
1259
1132
|
hasLocalVideo(sessionId) {
|
|
1260
|
-
return this.
|
|
1133
|
+
return hasLocalVideo(this.sipSessions[sessionId]);
|
|
1261
1134
|
}
|
|
1262
1135
|
// Check if we have at least one video track, even muted or not live
|
|
1263
1136
|
hasALocalVideoTrack(sessionId) {
|
|
1264
|
-
return this.
|
|
1137
|
+
return hasALocalVideoTrack(this.sipSessions[sessionId]);
|
|
1265
1138
|
}
|
|
1266
1139
|
getLocalVideoStream(sessionId) {
|
|
1267
|
-
return
|
|
1140
|
+
return getLocalVideoStream(this.sipSessions[sessionId]);
|
|
1268
1141
|
}
|
|
1269
1142
|
// Remote streams
|
|
1270
1143
|
getRemoteStream(sessionId) {
|
|
1271
|
-
|
|
1272
|
-
return sipSession?.sessionDescriptionHandler?.remoteMediaStream || null;
|
|
1144
|
+
return getRemoteStream(this.sipSessions[sessionId]);
|
|
1273
1145
|
}
|
|
1274
1146
|
getRemoteTracks(sessionId) {
|
|
1275
|
-
|
|
1276
|
-
if (!remoteStream) {
|
|
1277
|
-
return [];
|
|
1278
|
-
}
|
|
1279
|
-
return remoteStream.getTracks();
|
|
1147
|
+
return getRemoteTracks(this.sipSessions[sessionId]);
|
|
1280
1148
|
}
|
|
1281
1149
|
hasRemoteVideo(sessionId) {
|
|
1282
|
-
return this.
|
|
1150
|
+
return hasRemoteVideo(this.sipSessions[sessionId]);
|
|
1283
1151
|
}
|
|
1284
1152
|
// Check if we have at least one video track, even muted or not live
|
|
1285
1153
|
hasARemoteVideoTrack(sessionId) {
|
|
1286
|
-
return this.
|
|
1154
|
+
return hasARemoteVideoTrack(this.sipSessions[sessionId]);
|
|
1287
1155
|
}
|
|
1288
1156
|
getRemoteVideoStream(sessionId) {
|
|
1289
|
-
|
|
1290
|
-
return null;
|
|
1291
|
-
}
|
|
1292
|
-
return this.hasRemoteVideo(sessionId) ? this.getRemoteStream(sessionId) : null;
|
|
1157
|
+
return getRemoteVideoStream(this.sipSessions[sessionId]);
|
|
1293
1158
|
}
|
|
1294
1159
|
// Useful in a react-native environment when remoteMediaStream is not updated
|
|
1295
1160
|
getRemoteVideoStreamFromPc(sessionId) {
|
|
1296
|
-
|
|
1297
|
-
if (!pc) {
|
|
1298
|
-
return null;
|
|
1299
|
-
}
|
|
1300
|
-
return pc.getRemoteStreams().find((stream) => !!stream.getVideoTracks().length);
|
|
1161
|
+
return getRemoteVideoStreamFromPc(this.sipSessions[sessionId]);
|
|
1301
1162
|
}
|
|
1302
1163
|
hasVideo(sessionId) {
|
|
1303
|
-
return
|
|
1164
|
+
return hasVideoTrack(this.sipSessions[sessionId]);
|
|
1304
1165
|
}
|
|
1305
1166
|
hasAVideoTrack(sessionId) {
|
|
1306
|
-
return
|
|
1307
|
-
}
|
|
1308
|
-
getSipSessionId(sipSession) {
|
|
1309
|
-
if (!sipSession) {
|
|
1310
|
-
return '';
|
|
1311
|
-
}
|
|
1312
|
-
const message = sipSession.message;
|
|
1313
|
-
if (message?.callId) {
|
|
1314
|
-
return message.callId.substring(0, SIP_ID_LENGTH);
|
|
1315
|
-
}
|
|
1316
|
-
// For Inviter
|
|
1317
|
-
// @ts-ignore: private
|
|
1318
|
-
if (sipSession instanceof Inviter && sipSession.outgoingRequestMessage) {
|
|
1319
|
-
// @ts-ignore: private
|
|
1320
|
-
return sipSession.outgoingRequestMessage.callId.substring(0, SIP_ID_LENGTH);
|
|
1321
|
-
}
|
|
1322
|
-
// For Invitation
|
|
1323
|
-
return (sipSession.id || '').substring(0, SIP_ID_LENGTH);
|
|
1167
|
+
return hasAVideoTrack(this.sipSessions[sessionId]);
|
|
1324
1168
|
}
|
|
1325
1169
|
async waitForRegister() {
|
|
1326
1170
|
return new Promise(resolve => this.on(REGISTERED, resolve));
|
|
1327
1171
|
}
|
|
1328
|
-
// eslint-disable-next-line no-unused-vars
|
|
1329
|
-
sessionWantsToDoVideo(session) {
|
|
1330
|
-
if (!session) {
|
|
1331
|
-
return false;
|
|
1332
|
-
}
|
|
1333
|
-
const { body, } = session.request || session;
|
|
1334
|
-
// Sometimes with InviteClientContext the body is in the body attribute ...
|
|
1335
|
-
const sdp = typeof body === 'object' && body ? body.body : body;
|
|
1336
|
-
return hasAnActiveVideo(sdp);
|
|
1337
|
-
}
|
|
1338
1172
|
hasHeartbeat() {
|
|
1339
1173
|
return this.heartbeat.hasHeartbeat;
|
|
1340
1174
|
}
|
|
@@ -1360,7 +1194,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
1360
1194
|
}
|
|
1361
1195
|
onCallEnded(session) {
|
|
1362
1196
|
this._cleanupMedia(session);
|
|
1363
|
-
delete this.sipSessions[
|
|
1197
|
+
delete this.sipSessions[getSipSessionId(session)];
|
|
1364
1198
|
this._stopSendingStats(session);
|
|
1365
1199
|
this.stopNetworkMonitoring(session);
|
|
1366
1200
|
}
|
|
@@ -1373,7 +1207,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
1373
1207
|
this.userAgent.attemptReconnection();
|
|
1374
1208
|
}
|
|
1375
1209
|
storeSipSession(session) {
|
|
1376
|
-
const id =
|
|
1210
|
+
const id = getSipSessionId(session);
|
|
1377
1211
|
logger.info('storing sip session', { id, clientId: this.clientId });
|
|
1378
1212
|
this.sipSessions[id] = session;
|
|
1379
1213
|
}
|
|
@@ -1383,21 +1217,6 @@ export default class WebRTCClient extends Emitter {
|
|
|
1383
1217
|
getSipSessionIds() {
|
|
1384
1218
|
return Object.keys(this.sipSessions);
|
|
1385
1219
|
}
|
|
1386
|
-
setLocalMediaStream(sipSessionId, newStream) {
|
|
1387
|
-
const sipSession = this.sipSessions[sipSessionId];
|
|
1388
|
-
if (!sipSession) {
|
|
1389
|
-
return;
|
|
1390
|
-
}
|
|
1391
|
-
logger.info('setting local media stream', {
|
|
1392
|
-
sipSessionId,
|
|
1393
|
-
tracks: newStream ? newStream.getTracks() : null,
|
|
1394
|
-
});
|
|
1395
|
-
if (sipSession.sessionDescriptionHandler) {
|
|
1396
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
1397
|
-
// @ts-ignore: protected
|
|
1398
|
-
sipSession.sessionDescriptionHandler._localMediaStream = newStream;
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
1220
|
async updateLocalStream(sipSessionId, newStream) {
|
|
1402
1221
|
const sipSession = this.sipSessions[sipSessionId];
|
|
1403
1222
|
if (!sipSession) {
|
|
@@ -1410,76 +1229,12 @@ export default class WebRTCClient extends Emitter {
|
|
|
1410
1229
|
tracks: newStream.getTracks(),
|
|
1411
1230
|
});
|
|
1412
1231
|
if (oldStream) {
|
|
1413
|
-
|
|
1232
|
+
cleanupStream(oldStream);
|
|
1414
1233
|
}
|
|
1415
|
-
|
|
1234
|
+
setLocalMediaStream(sipSession, newStream);
|
|
1416
1235
|
// Update the local video element
|
|
1417
1236
|
await this._setupMedias(sipSession, newStream);
|
|
1418
1237
|
}
|
|
1419
|
-
updateRemoteStream(sessionId, allTracks = true) {
|
|
1420
|
-
const remoteStream = this.getRemoteStream(sessionId);
|
|
1421
|
-
const pc = this.getPeerConnection(sessionId);
|
|
1422
|
-
logger.info('Updating remote stream', {
|
|
1423
|
-
sessionId,
|
|
1424
|
-
tracks: remoteStream ? remoteStream.getTracks() : null,
|
|
1425
|
-
receiverTracks: pc && pc.getReceivers ? pc.getReceivers().map((receiver) => receiver.track) : null,
|
|
1426
|
-
});
|
|
1427
|
-
if (!pc || !remoteStream) {
|
|
1428
|
-
return;
|
|
1429
|
-
}
|
|
1430
|
-
remoteStream.getTracks().forEach(track => {
|
|
1431
|
-
if (allTracks || track.kind === 'video') {
|
|
1432
|
-
remoteStream.removeTrack(track);
|
|
1433
|
-
}
|
|
1434
|
-
});
|
|
1435
|
-
if (pc.getReceivers) {
|
|
1436
|
-
pc.getReceivers().forEach((receiver) => {
|
|
1437
|
-
if (allTracks || receiver.track.kind === 'video') {
|
|
1438
|
-
remoteStream.addTrack(receiver.track);
|
|
1439
|
-
}
|
|
1440
|
-
});
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
getMediaConfiguration(enableVideo, conference = false, constraints = null) {
|
|
1444
|
-
const screenSharing = constraints && 'screen' in constraints ? constraints.screen : false;
|
|
1445
|
-
const isDesktop = constraints && 'desktop' in constraints ? constraints.desktop : false;
|
|
1446
|
-
const withAudio = constraints && 'audio' in constraints ? constraints.audio : true;
|
|
1447
|
-
const mandatoryVideo = constraints && typeof constraints.video === 'object' ? constraints.video.mandatory : {};
|
|
1448
|
-
logger.info('Retrieving media a configuration', {
|
|
1449
|
-
enableVideo,
|
|
1450
|
-
screenSharing,
|
|
1451
|
-
isDesktop,
|
|
1452
|
-
withAudio,
|
|
1453
|
-
constraints,
|
|
1454
|
-
});
|
|
1455
|
-
return {
|
|
1456
|
-
constraints: {
|
|
1457
|
-
// Exact constraint are not supported with `getDisplayMedia` and we must have a video=false in desktop screenshare
|
|
1458
|
-
audio: screenSharing ? !isDesktop : withAudio ? this._getAudioConstraints() : false,
|
|
1459
|
-
video: screenSharing ? isDesktop ? {
|
|
1460
|
-
mandatory: {
|
|
1461
|
-
chromeMediaSource: 'desktop',
|
|
1462
|
-
...(mandatoryVideo || {}),
|
|
1463
|
-
},
|
|
1464
|
-
} : {
|
|
1465
|
-
cursor: 'always',
|
|
1466
|
-
} : this._getVideoConstraints(enableVideo),
|
|
1467
|
-
screen: screenSharing,
|
|
1468
|
-
desktop: isDesktop,
|
|
1469
|
-
conference,
|
|
1470
|
-
},
|
|
1471
|
-
enableVideo,
|
|
1472
|
-
conference,
|
|
1473
|
-
offerOptions: {
|
|
1474
|
-
OfferToReceiveAudio: this._hasAudio(),
|
|
1475
|
-
OfferToReceiveVideo: enableVideo,
|
|
1476
|
-
mandatory: {
|
|
1477
|
-
OfferToReceiveAudio: this._hasAudio(),
|
|
1478
|
-
OfferToReceiveVideo: enableVideo,
|
|
1479
|
-
},
|
|
1480
|
-
},
|
|
1481
|
-
};
|
|
1482
|
-
}
|
|
1483
1238
|
isConference(sessionId) {
|
|
1484
1239
|
return sessionId in this.conferences;
|
|
1485
1240
|
}
|
|
@@ -1555,25 +1310,14 @@ export default class WebRTCClient extends Emitter {
|
|
|
1555
1310
|
this._onTransportError();
|
|
1556
1311
|
}
|
|
1557
1312
|
}
|
|
1558
|
-
_isWeb() {
|
|
1559
|
-
return typeof window === 'object' && typeof document === 'object';
|
|
1560
|
-
}
|
|
1561
|
-
_isVideoTrack(track) {
|
|
1562
|
-
return track.kind === 'video' && track.readyState === 'live';
|
|
1563
|
-
}
|
|
1564
1313
|
_hasAudio() {
|
|
1565
1314
|
return this.hasAudio;
|
|
1566
1315
|
}
|
|
1567
1316
|
_getAudioConstraints() {
|
|
1568
|
-
|
|
1569
|
-
return this.audio?.deviceId?.exact ? this.audio : true;
|
|
1317
|
+
return getAudioConstraints(this.audio);
|
|
1570
1318
|
}
|
|
1571
1319
|
_getVideoConstraints(video = false) {
|
|
1572
|
-
|
|
1573
|
-
return false;
|
|
1574
|
-
}
|
|
1575
|
-
// @ts-ignore: media constraints
|
|
1576
|
-
return this.video?.deviceId?.exact ? this.video : true;
|
|
1320
|
+
return getVideoConstraints(video && this.video);
|
|
1577
1321
|
}
|
|
1578
1322
|
_connectIfNeeded() {
|
|
1579
1323
|
logger.info('connect if needed, checking', { connected: this.isConnected(), clientId: this.clientId });
|
|
@@ -1623,97 +1367,16 @@ export default class WebRTCClient extends Emitter {
|
|
|
1623
1367
|
...config,
|
|
1624
1368
|
}));
|
|
1625
1369
|
}
|
|
1626
|
-
_createUaOptions(uaOptionsOverrides = {}) {
|
|
1627
|
-
let { host, } = this.config;
|
|
1628
|
-
let port = this.config.port || 443;
|
|
1629
|
-
if (this.config.websocketSip) {
|
|
1630
|
-
const webSocketSip = this.config.websocketSip.split(':');
|
|
1631
|
-
[host] = webSocketSip;
|
|
1632
|
-
port = Number(webSocketSip[1]);
|
|
1633
|
-
}
|
|
1634
|
-
const { userUuid } = this.config;
|
|
1635
|
-
const uaOptions = {
|
|
1636
|
-
noAnswerTimeout: NO_ANSWER_TIMEOUT,
|
|
1637
|
-
authorizationUsername: this.config.authorizationUser,
|
|
1638
|
-
authorizationPassword: this.config.password,
|
|
1639
|
-
displayName: this.config.displayName,
|
|
1640
|
-
hackIpInContact: true,
|
|
1641
|
-
contactParams: {
|
|
1642
|
-
transport: 'wss',
|
|
1643
|
-
},
|
|
1644
|
-
logBuiltinEnabled: this.config.log ? this.config.log.builtinEnabled : null,
|
|
1645
|
-
logLevel: this.config.log ? this.config.log.logLevel : null,
|
|
1646
|
-
logConnector: this.config.log ? this.config.log.connector : null,
|
|
1647
|
-
uri: this._makeURI(this.config.authorizationUser || ''),
|
|
1648
|
-
userAgentString: this.config.userAgentString || 'wazo-sdk',
|
|
1649
|
-
reconnectionAttempts: 10000,
|
|
1650
|
-
reconnectionDelay: 5,
|
|
1651
|
-
sessionDescriptionHandlerFactory: (session, options = {}) => {
|
|
1652
|
-
const uaLogger = session.userAgent.getLogger('sip.WazoSessionDescriptionHandler');
|
|
1653
|
-
const isWeb = this._isWeb();
|
|
1654
|
-
const iceGatheringTimeout = 'peerConnectionOptions' in options ? options.peerConnectionOptions.iceGatheringTimeout || DEFAULT_ICE_TIMEOUT : DEFAULT_ICE_TIMEOUT;
|
|
1655
|
-
const sdhOptions = { ...options,
|
|
1656
|
-
iceGatheringTimeout,
|
|
1657
|
-
peerConnectionConfiguration: { ...defaultPeerConnectionConfiguration(),
|
|
1658
|
-
...(options.peerConnectionConfiguration || {}),
|
|
1659
|
-
},
|
|
1660
|
-
};
|
|
1661
|
-
return new WazoSessionDescriptionHandler(uaLogger, wazoMediaStreamFactory, sdhOptions, isWeb, session);
|
|
1662
|
-
},
|
|
1663
|
-
transportOptions: {
|
|
1664
|
-
traceSip: uaOptionsOverrides?.traceSip || false,
|
|
1665
|
-
wsServers: `wss://${host}:${port}/api/asterisk/ws${userUuid ? `?userUuid=${userUuid}` : ''}`,
|
|
1666
|
-
},
|
|
1667
|
-
sessionDescriptionHandlerFactoryOptions: {
|
|
1668
|
-
modifiers: [replaceLocalIpModifier],
|
|
1669
|
-
alwaysAcquireMediaFirst: this.isFirefox(),
|
|
1670
|
-
constraints: {
|
|
1671
|
-
audio: this._getAudioConstraints(),
|
|
1672
|
-
video: this._getVideoConstraints(),
|
|
1673
|
-
},
|
|
1674
|
-
peerConnectionOptions: {
|
|
1675
|
-
iceCheckingTimeout: this.config.iceCheckingTimeout || 1000,
|
|
1676
|
-
iceGatheringTimeout: this.config.iceCheckingTimeout || 1000,
|
|
1677
|
-
rtcConfiguration: {
|
|
1678
|
-
rtcpMuxPolicy: 'require',
|
|
1679
|
-
iceServers: WebRTCClient.getIceServers(this.config.host),
|
|
1680
|
-
...this._getRtcOptions(),
|
|
1681
|
-
...(uaOptionsOverrides?.peerConnectionOptions || {}),
|
|
1682
|
-
},
|
|
1683
|
-
},
|
|
1684
|
-
// Configuration used in SDH to create the PeerConnection
|
|
1685
|
-
peerConnectionConfiguration: {
|
|
1686
|
-
rtcpMuxPolicy: 'require',
|
|
1687
|
-
iceServers: WebRTCClient.getIceServers(this.config.host),
|
|
1688
|
-
...(uaOptionsOverrides?.peerConnectionOptions || {}),
|
|
1689
|
-
},
|
|
1690
|
-
},
|
|
1691
|
-
};
|
|
1692
|
-
delete uaOptionsOverrides?.traceSip;
|
|
1693
|
-
return {
|
|
1694
|
-
...uaOptions,
|
|
1695
|
-
...uaOptionsOverrides,
|
|
1696
|
-
};
|
|
1697
|
-
}
|
|
1698
|
-
// eslint-disable-next-line no-unused-vars
|
|
1699
|
-
_getRtcOptions() {
|
|
1700
|
-
return {
|
|
1701
|
-
mandatory: {
|
|
1702
|
-
OfferToReceiveAudio: this._hasAudio(),
|
|
1703
|
-
OfferToReceiveVideo: false,
|
|
1704
|
-
},
|
|
1705
|
-
};
|
|
1706
|
-
}
|
|
1707
1370
|
// Invitation and Inviter extends Session
|
|
1708
1371
|
_setupSession(session) {
|
|
1709
|
-
const sipSessionId =
|
|
1372
|
+
const sipSessionId = getSipSessionId(session);
|
|
1710
1373
|
// When receiving an Invitation, the delegate is not defined.
|
|
1711
1374
|
if (!session.delegate) {
|
|
1712
1375
|
session.delegate = {};
|
|
1713
1376
|
}
|
|
1714
1377
|
session.delegate.onSessionDescriptionHandler = (sdh) => {
|
|
1715
1378
|
sdh.on('error', (e) => {
|
|
1716
|
-
this.eventEmitter.emit(ON_ERROR, e);
|
|
1379
|
+
this.eventEmitter.emit(ON_ERROR, e, session);
|
|
1717
1380
|
});
|
|
1718
1381
|
sdh.peerConnectionDelegate = {
|
|
1719
1382
|
onicecandidateerror: (error) => {
|
|
@@ -1762,15 +1425,15 @@ export default class WebRTCClient extends Emitter {
|
|
|
1762
1425
|
// @ts-ignore: private
|
|
1763
1426
|
session.outgoingInviteRequest.message.body.body = request.body;
|
|
1764
1427
|
}
|
|
1765
|
-
this.
|
|
1428
|
+
updateRemoteStream(this.sipSessions[sipSessionId], false);
|
|
1766
1429
|
await this._setupMedias(session);
|
|
1767
1430
|
return this.eventEmitter.emit(ON_REINVITE, session, request, updatedCalleeName, updatedNumber, hadRemoteVideo);
|
|
1768
1431
|
};
|
|
1769
1432
|
}
|
|
1770
1433
|
async _onProgress(session, early = false) {
|
|
1771
1434
|
await this._setupMedias(session);
|
|
1772
|
-
const sessionId =
|
|
1773
|
-
|
|
1435
|
+
const sessionId = getSipSessionId(session).substr(0, 20);
|
|
1436
|
+
updateRemoteStream(session);
|
|
1774
1437
|
logger.info('progress received', {
|
|
1775
1438
|
sessionId,
|
|
1776
1439
|
early,
|
|
@@ -1785,21 +1448,14 @@ export default class WebRTCClient extends Emitter {
|
|
|
1785
1448
|
});
|
|
1786
1449
|
this.storeSipSession(session);
|
|
1787
1450
|
await this._setupMedias(session);
|
|
1788
|
-
|
|
1789
|
-
const pc = session
|
|
1451
|
+
updateRemoteStream(session, initAllTracks);
|
|
1452
|
+
const pc = getPeerConnection(session);
|
|
1790
1453
|
const onTrack = (event) => {
|
|
1791
|
-
const
|
|
1792
|
-
const { kind, label, readyState, id, muted
|
|
1793
|
-
logger.info('on track event', {
|
|
1794
|
-
isAudioOnly,
|
|
1795
|
-
kind,
|
|
1796
|
-
label,
|
|
1797
|
-
readyState,
|
|
1798
|
-
id,
|
|
1799
|
-
muted,
|
|
1800
|
-
});
|
|
1454
|
+
const audioOnly = isAudioOnly(session);
|
|
1455
|
+
const { kind, label, readyState, id, muted } = event.track;
|
|
1456
|
+
logger.info('on track event', { audioOnly, kind, label, readyState, id, muted });
|
|
1801
1457
|
// Stop video track in audio only mode
|
|
1802
|
-
if (
|
|
1458
|
+
if (audioOnly && kind === 'video') {
|
|
1803
1459
|
event.track.stop();
|
|
1804
1460
|
}
|
|
1805
1461
|
this.eventEmitter.emit(ON_TRACK, session, event);
|
|
@@ -1824,16 +1480,13 @@ export default class WebRTCClient extends Emitter {
|
|
|
1824
1480
|
}
|
|
1825
1481
|
this._startSendingStats(session);
|
|
1826
1482
|
}
|
|
1827
|
-
_isAudioOnly(session) {
|
|
1828
|
-
return Boolean(session.sessionDescriptionHandlerModifiersReInvite.find(modifier => modifier === stripVideo));
|
|
1829
|
-
}
|
|
1830
1483
|
async _setupMedias(session, newStream = null) {
|
|
1831
|
-
if (!
|
|
1484
|
+
if (!isWeb()) {
|
|
1832
1485
|
logger.info('Setup media on mobile, no need to setup html element, bailing');
|
|
1833
1486
|
return;
|
|
1834
1487
|
}
|
|
1835
1488
|
// Safari hack, because you cannot call .play() from a non user action
|
|
1836
|
-
const sessionId =
|
|
1489
|
+
const sessionId = getSipSessionId(session);
|
|
1837
1490
|
const isConference = this.isConference(sessionId);
|
|
1838
1491
|
if (sessionId in this.audioElements) {
|
|
1839
1492
|
logger.info('html element already exists for session', {
|
|
@@ -1841,7 +1494,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
1841
1494
|
});
|
|
1842
1495
|
return;
|
|
1843
1496
|
}
|
|
1844
|
-
if (this._hasAudio() &&
|
|
1497
|
+
if (this._hasAudio() && isWeb() && !(sessionId in this.audioElements)) {
|
|
1845
1498
|
await this.createAudioElementFor(sessionId);
|
|
1846
1499
|
}
|
|
1847
1500
|
const audioElement = this.audioElements[sessionId];
|
|
@@ -1878,10 +1531,10 @@ export default class WebRTCClient extends Emitter {
|
|
|
1878
1531
|
audioElement.play().catch(() => { });
|
|
1879
1532
|
}
|
|
1880
1533
|
_cleanupMedia(session) {
|
|
1881
|
-
const sessionId =
|
|
1534
|
+
const sessionId = getSipSessionId(session);
|
|
1882
1535
|
const localStream = this.getLocalStream(sessionId);
|
|
1883
1536
|
if (localStream) {
|
|
1884
|
-
|
|
1537
|
+
cleanupStream(localStream);
|
|
1885
1538
|
}
|
|
1886
1539
|
const cleanLocalElement = (id) => {
|
|
1887
1540
|
const element = this.audioElements[id];
|
|
@@ -1895,80 +1548,15 @@ export default class WebRTCClient extends Emitter {
|
|
|
1895
1548
|
element.srcObject = null;
|
|
1896
1549
|
delete this.audioElements[id];
|
|
1897
1550
|
};
|
|
1898
|
-
if (this._hasAudio() &&
|
|
1551
|
+
if (this._hasAudio() && isWeb()) {
|
|
1899
1552
|
if (session) {
|
|
1900
|
-
cleanLocalElement(
|
|
1553
|
+
cleanLocalElement(getSipSessionId(session));
|
|
1901
1554
|
}
|
|
1902
1555
|
else {
|
|
1903
1556
|
Object.keys(this.audioElements).forEach(id => cleanLocalElement(id));
|
|
1904
1557
|
}
|
|
1905
1558
|
}
|
|
1906
1559
|
}
|
|
1907
|
-
_cleanupStream(stream) {
|
|
1908
|
-
stream.getTracks().filter(track => track.enabled).forEach(track => track.stop());
|
|
1909
|
-
}
|
|
1910
|
-
_toggleAudio(session, muteAudio) {
|
|
1911
|
-
const sdh = session.sessionDescriptionHandler;
|
|
1912
|
-
const pc = (sdh?.peerConnection || null);
|
|
1913
|
-
if (!pc) {
|
|
1914
|
-
return;
|
|
1915
|
-
}
|
|
1916
|
-
if (pc.getSenders) {
|
|
1917
|
-
pc.getSenders().forEach((sender) => {
|
|
1918
|
-
if (sender && sender.track && sender.track.kind === 'audio') {
|
|
1919
|
-
// eslint-disable-next-line
|
|
1920
|
-
sender.track.enabled = !muteAudio;
|
|
1921
|
-
}
|
|
1922
|
-
});
|
|
1923
|
-
}
|
|
1924
|
-
else {
|
|
1925
|
-
pc.getLocalStreams().forEach((stream) => {
|
|
1926
|
-
stream.getAudioTracks().forEach(track => {
|
|
1927
|
-
// eslint-disable-next-line
|
|
1928
|
-
track.enabled = !muteAudio;
|
|
1929
|
-
});
|
|
1930
|
-
});
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
_toggleVideo(session, muteCamera) {
|
|
1934
|
-
const sdh = session.sessionDescriptionHandler;
|
|
1935
|
-
const pc = sdh?.peerConnection;
|
|
1936
|
-
if (pc?.getSenders) {
|
|
1937
|
-
pc.getSenders().forEach((sender) => {
|
|
1938
|
-
if (sender && sender.track && sender.track.kind === 'video') {
|
|
1939
|
-
// eslint-disable-next-line
|
|
1940
|
-
sender.track.enabled = !muteCamera;
|
|
1941
|
-
}
|
|
1942
|
-
});
|
|
1943
|
-
}
|
|
1944
|
-
else {
|
|
1945
|
-
pc.getLocalStreams().forEach((stream) => {
|
|
1946
|
-
stream.getVideoTracks().forEach(track => {
|
|
1947
|
-
// eslint-disable-next-line
|
|
1948
|
-
track.enabled = !muteCamera;
|
|
1949
|
-
});
|
|
1950
|
-
});
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
/**
|
|
1954
|
-
* @param pc RTCPeerConnection
|
|
1955
|
-
*/
|
|
1956
|
-
_getRemoteStream(pc) {
|
|
1957
|
-
let remoteStream = null;
|
|
1958
|
-
if (pc && pc.getReceivers) {
|
|
1959
|
-
remoteStream = typeof global !== 'undefined' && global.window && global.window.MediaStream ? new global.window.MediaStream() : new window.MediaStream();
|
|
1960
|
-
pc.getReceivers().forEach((receiver) => {
|
|
1961
|
-
const { track, } = receiver;
|
|
1962
|
-
if (track && remoteStream) {
|
|
1963
|
-
remoteStream.addTrack(track);
|
|
1964
|
-
}
|
|
1965
|
-
});
|
|
1966
|
-
}
|
|
1967
|
-
else if (pc) {
|
|
1968
|
-
[remoteStream] = pc.getRemoteStreams();
|
|
1969
|
-
}
|
|
1970
|
-
return remoteStream;
|
|
1971
|
-
}
|
|
1972
1560
|
_cleanupRegister() {
|
|
1973
1561
|
if (this.registerer) {
|
|
1974
1562
|
// @ts-ignore: removeAllListeners does not exist
|
|
@@ -1982,7 +1570,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
1982
1570
|
if (!pc) {
|
|
1983
1571
|
return;
|
|
1984
1572
|
}
|
|
1985
|
-
const sessionId =
|
|
1573
|
+
const sessionId = getSipSessionId(session);
|
|
1986
1574
|
getStats(pc, (result) => {
|
|
1987
1575
|
const { results, internal, nomore, ...stats } = result;
|
|
1988
1576
|
this.statsIntervals[sessionId] = nomore;
|
|
@@ -1993,7 +1581,7 @@ export default class WebRTCClient extends Emitter {
|
|
|
1993
1581
|
}, SEND_STATS_DELAY);
|
|
1994
1582
|
}
|
|
1995
1583
|
_stopSendingStats(session) {
|
|
1996
|
-
const sessionId =
|
|
1584
|
+
const sessionId = getSipSessionId(session);
|
|
1997
1585
|
logger.trace('Check for stopping stats', {
|
|
1998
1586
|
sessionId,
|
|
1999
1587
|
ids: Object.keys(this.statsIntervals),
|
|
@@ -2006,9 +1594,6 @@ export default class WebRTCClient extends Emitter {
|
|
|
2006
1594
|
delete this.statsIntervals[sessionId];
|
|
2007
1595
|
}
|
|
2008
1596
|
}
|
|
2009
|
-
_makeURI(target) {
|
|
2010
|
-
return UserAgent.makeURI(`sip:${target}@${this.config.host}`);
|
|
2011
|
-
}
|
|
2012
1597
|
async _disconnectTransport(force = false) {
|
|
2013
1598
|
const transport = this.userAgent?.transport;
|
|
2014
1599
|
if (force && transport) {
|
|
@@ -2030,96 +1615,13 @@ export default class WebRTCClient extends Emitter {
|
|
|
2030
1615
|
}
|
|
2031
1616
|
}
|
|
2032
1617
|
async _fetchNetworkStats(sessionId) {
|
|
2033
|
-
const session = this.
|
|
2034
|
-
|
|
2035
|
-
if (!stats || !(sessionId in this.sessionNetworkStats)) {
|
|
1618
|
+
const session = this.sipSessions[sessionId];
|
|
1619
|
+
if (!(sessionId in this.sessionNetworkStats)) {
|
|
2036
1620
|
return Promise.resolve(null);
|
|
2037
1621
|
}
|
|
2038
|
-
const networkStats = {
|
|
2039
|
-
audioBytesSent: 0,
|
|
2040
|
-
videoBytesSent: 0,
|
|
2041
|
-
videoBytesReceived: 0,
|
|
2042
|
-
audioBytesReceived: 0,
|
|
2043
|
-
};
|
|
2044
1622
|
const nbStats = this.sessionNetworkStats[sessionId].length;
|
|
2045
1623
|
const lastNetworkStats = this.sessionNetworkStats[sessionId][nbStats - 1];
|
|
2046
|
-
const
|
|
2047
|
-
const lastAudioContentSent = lastNetworkStats ? lastNetworkStats.audioContentSent : 0;
|
|
2048
|
-
const lastVideoSent = lastNetworkStats ? lastNetworkStats.videoBytesSent : 0;
|
|
2049
|
-
const lastAudioReceived = lastNetworkStats ? lastNetworkStats.audioBytesReceived : 0;
|
|
2050
|
-
const lastAudioContentReceived = lastNetworkStats ? lastNetworkStats.audioContentReceived : 0;
|
|
2051
|
-
const lastVideoReceived = lastNetworkStats ? lastNetworkStats.videoBytesReceived : 0;
|
|
2052
|
-
const lastTransportSent = lastNetworkStats ? lastNetworkStats.transportSent : 0;
|
|
2053
|
-
const lastTransportReceived = lastNetworkStats ? lastNetworkStats.transportReceived : 0;
|
|
2054
|
-
let audioBytesSent = 0; // content + header
|
|
2055
|
-
let audioBytesReceived = 0; // content + header
|
|
2056
|
-
let audioContentSent = 0; // useful to detect blank call
|
|
2057
|
-
let audioContentReceived = 0;
|
|
2058
|
-
let videoBytesSent = 0;
|
|
2059
|
-
let videoBytesReceived = 0;
|
|
2060
|
-
let transportSent = 0;
|
|
2061
|
-
let transportReceived = 0;
|
|
2062
|
-
let packetsLost = 0;
|
|
2063
|
-
stats.forEach(report => {
|
|
2064
|
-
if (report.type === 'outbound-rtp' && report.kind === 'audio') {
|
|
2065
|
-
audioBytesSent += Number(report.bytesSent) + Number(report.headerBytesSent);
|
|
2066
|
-
audioContentSent += Number(report.bytesSent);
|
|
2067
|
-
}
|
|
2068
|
-
if (report.type === 'remote-outbound-rtp' && report.kind === 'audio') {
|
|
2069
|
-
audioBytesSent += Number(report.bytesSent);
|
|
2070
|
-
}
|
|
2071
|
-
if (report.type === 'outbound-rtp' && report.kind === 'video') {
|
|
2072
|
-
videoBytesSent += Number(report.bytesSent) + Number(report.headerBytesSent);
|
|
2073
|
-
}
|
|
2074
|
-
if (report.type === 'inbound-rtp' && report.kind === 'audio') {
|
|
2075
|
-
packetsLost += Number(report.packetsLost);
|
|
2076
|
-
networkStats.packetsReceived = Number(report.packetsReceived);
|
|
2077
|
-
audioBytesReceived += Number(report.bytesReceived) + Number(report.headerBytesReceived);
|
|
2078
|
-
audioContentReceived += Number(report.bytesReceived);
|
|
2079
|
-
}
|
|
2080
|
-
if (report.type === 'inbound-rtp' && report.kind === 'video') {
|
|
2081
|
-
videoBytesReceived += Number(report.bytesReceived) + Number(report.headerBytesReceived);
|
|
2082
|
-
}
|
|
2083
|
-
if (report.type === 'outbound-rtp' && report.kind === 'video') {
|
|
2084
|
-
if ('framesPerSecond' in report) {
|
|
2085
|
-
networkStats.framesPerSecond = Number(report.framesPerSecond);
|
|
2086
|
-
}
|
|
2087
|
-
if ('framerateMean' in report) {
|
|
2088
|
-
// framerateMean is only available in FF
|
|
2089
|
-
networkStats.framesPerSecond = Math.round(report.framerateMean);
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
2092
|
-
if (report.type === 'remote-inbound-rtp' && report.kind === 'audio') {
|
|
2093
|
-
packetsLost += Number(report.packetsLost);
|
|
2094
|
-
networkStats.roundTripTime = Number(report.roundTripTime);
|
|
2095
|
-
networkStats.jitter = Number(report.jitter);
|
|
2096
|
-
}
|
|
2097
|
-
if (report.type === 'remote-inbound-rtp' && report.kind === 'video') {
|
|
2098
|
-
packetsLost += Number(report.packetsLost);
|
|
2099
|
-
}
|
|
2100
|
-
if (report.type === 'transport') {
|
|
2101
|
-
transportSent += Number(report.bytesSent);
|
|
2102
|
-
transportReceived += Number(report.bytesReceived);
|
|
2103
|
-
}
|
|
2104
|
-
});
|
|
2105
|
-
networkStats.packetsLost = packetsLost;
|
|
2106
|
-
networkStats.totalAudioBytesSent = audioBytesSent;
|
|
2107
|
-
networkStats.audioBytesSent = audioBytesSent - lastAudioSent;
|
|
2108
|
-
networkStats.totalAudioBytesReceived = audioBytesReceived - lastAudioReceived;
|
|
2109
|
-
networkStats.audioBytesReceived = audioBytesReceived;
|
|
2110
|
-
networkStats.totalAudioContentSent = audioContentSent;
|
|
2111
|
-
networkStats.audioContentSent = audioContentSent - lastAudioContentSent;
|
|
2112
|
-
networkStats.totalAudioBytesReceived = audioContentReceived - lastAudioContentReceived;
|
|
2113
|
-
networkStats.audioContentReceived = audioContentReceived;
|
|
2114
|
-
networkStats.totalVideoBytesSent = videoBytesSent;
|
|
2115
|
-
networkStats.videoBytesSent = videoBytesSent - lastVideoSent;
|
|
2116
|
-
networkStats.totalVideoBytesReceived = videoBytesReceived;
|
|
2117
|
-
networkStats.videoBytesReceived = videoBytesReceived - lastVideoReceived;
|
|
2118
|
-
networkStats.totalTransportSent = transportSent - lastTransportSent;
|
|
2119
|
-
networkStats.transportSent = transportSent;
|
|
2120
|
-
networkStats.totalTransportReceived = transportReceived;
|
|
2121
|
-
networkStats.transportReceived = transportReceived - lastTransportReceived;
|
|
2122
|
-
networkStats.bandwidth = networkStats.audioBytesSent + networkStats.audioBytesReceived + networkStats.videoBytesSent + networkStats.videoBytesReceived + networkStats.transportReceived + networkStats.transportSent;
|
|
1624
|
+
const networkStats = await fetchNetworkStats(session, lastNetworkStats);
|
|
2123
1625
|
if (this.sessionNetworkStats[sessionId]) {
|
|
2124
1626
|
this.eventEmitter.emit(ON_NETWORK_STATS, session, networkStats, this.sessionNetworkStats[sessionId]);
|
|
2125
1627
|
if (sessionId in this.sessionNetworkStats) {
|