@voicenter-team/opensips-js 1.0.10

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.
Files changed (49) hide show
  1. package/README.md +75 -0
  2. package/build/enum/call.event.listener.type.d.ts +7 -0
  3. package/build/enum/call.event.listener.type.js +10 -0
  4. package/build/enum/metric.keys.to.include.d.ts +2 -0
  5. package/build/enum/metric.keys.to.include.js +4 -0
  6. package/build/helpers/UA/index.d.ts +6 -0
  7. package/build/helpers/UA/index.js +9 -0
  8. package/build/helpers/audio.helper.d.ts +9 -0
  9. package/build/helpers/audio.helper.js +60 -0
  10. package/build/helpers/filter.helper.d.ts +2 -0
  11. package/build/helpers/filter.helper.js +14 -0
  12. package/build/helpers/time.helper.d.ts +16 -0
  13. package/build/helpers/time.helper.js +28 -0
  14. package/build/helpers/volume.helper.d.ts +2 -0
  15. package/build/helpers/volume.helper.js +76 -0
  16. package/build/helpers/webrtcmetrics/collector.d.ts +32 -0
  17. package/build/helpers/webrtcmetrics/collector.js +282 -0
  18. package/build/helpers/webrtcmetrics/engine.d.ts +20 -0
  19. package/build/helpers/webrtcmetrics/engine.js +164 -0
  20. package/build/helpers/webrtcmetrics/exporter.d.ts +116 -0
  21. package/build/helpers/webrtcmetrics/exporter.js +528 -0
  22. package/build/helpers/webrtcmetrics/extractor.d.ts +1 -0
  23. package/build/helpers/webrtcmetrics/extractor.js +976 -0
  24. package/build/helpers/webrtcmetrics/index.d.ts +63 -0
  25. package/build/helpers/webrtcmetrics/index.js +93 -0
  26. package/build/helpers/webrtcmetrics/metrics.d.ts +2 -0
  27. package/build/helpers/webrtcmetrics/metrics.js +8 -0
  28. package/build/helpers/webrtcmetrics/probe.d.ts +76 -0
  29. package/build/helpers/webrtcmetrics/probe.js +153 -0
  30. package/build/helpers/webrtcmetrics/utils/config.d.ts +12 -0
  31. package/build/helpers/webrtcmetrics/utils/config.js +28 -0
  32. package/build/helpers/webrtcmetrics/utils/helper.d.ts +13 -0
  33. package/build/helpers/webrtcmetrics/utils/helper.js +134 -0
  34. package/build/helpers/webrtcmetrics/utils/log.d.ts +7 -0
  35. package/build/helpers/webrtcmetrics/utils/log.js +71 -0
  36. package/build/helpers/webrtcmetrics/utils/models.d.ts +309 -0
  37. package/build/helpers/webrtcmetrics/utils/models.js +298 -0
  38. package/build/helpers/webrtcmetrics/utils/score.d.ts +4 -0
  39. package/build/helpers/webrtcmetrics/utils/score.js +235 -0
  40. package/build/helpers/webrtcmetrics/utils/shortUUId.d.ts +1 -0
  41. package/build/helpers/webrtcmetrics/utils/shortUUId.js +7 -0
  42. package/build/index.d.ts +170 -0
  43. package/build/index.js +849 -0
  44. package/package.json +61 -0
  45. package/src/types/declarations.d.ts +6 -0
  46. package/src/types/generic.d.ts +1 -0
  47. package/src/types/listeners.d.ts +42 -0
  48. package/src/types/rtc.d.ts +133 -0
  49. package/src/types/webrtcmetrics.d.ts +64 -0
package/build/index.js ADDED
@@ -0,0 +1,849 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const jssip_1 = __importDefault(require("jssip"));
16
+ const UA_1 = __importDefault(require("./helpers/UA"));
17
+ const p_iteration_1 = require("p-iteration");
18
+ const time_helper_1 = require("./helpers/time.helper");
19
+ const filter_helper_1 = require("./helpers/filter.helper");
20
+ const audio_helper_1 = require("./helpers/audio.helper");
21
+ const metrics_1 = __importDefault(require("./helpers/webrtcmetrics/metrics"));
22
+ const metric_keys_to_include_1 = require("./enum/metric.keys.to.include");
23
+ const call_event_listener_type_1 = require("./enum/call.event.listener.type");
24
+ const CALL_STATUS_UNANSWERED = 0;
25
+ const STORAGE_KEYS = {
26
+ SELECTED_INPUT_DEVICE: 'selectedInputDevice',
27
+ SELECTED_OUTPUT_DEVICE: 'selectedOutputDevice'
28
+ };
29
+ const activeCalls = {};
30
+ class OpenSIPSJS extends UA_1.default {
31
+ constructor(options) {
32
+ const configuration = Object.assign(Object.assign({}, options.configuration), { sockets: options.socketInterfaces.map(sock => new jssip_1.default.WebSocketInterface(sock)) });
33
+ super(configuration);
34
+ this.initialized = false;
35
+ this.newRTCSessionEventName = 'newRTCSession';
36
+ this.activeCalls = {};
37
+ this.state = {
38
+ isMuted: false,
39
+ activeCalls: {},
40
+ availableMediaDevices: [],
41
+ selectedMediaDevices: {
42
+ input: 'default',
43
+ output: 'default' //localStorage.getItem(STORAGE_KEYS.SELECTED_OUTPUT_DEVICE) || 'default'
44
+ },
45
+ microphoneInputLevel: 2,
46
+ speakerVolume: 1,
47
+ muteWhenJoin: false,
48
+ originalStream: null,
49
+ isDND: false,
50
+ listeners: {},
51
+ activeRooms: {},
52
+ callStatus: {},
53
+ callTime: {},
54
+ timeIntervals: {},
55
+ callMetrics: {},
56
+ metricConfig: {
57
+ refreshEvery: 1000,
58
+ }
59
+ };
60
+ this.options = options;
61
+ }
62
+ on(type, listener) {
63
+ return super.on(type, listener);
64
+ }
65
+ off(type, listener) {
66
+ return super.off(type, listener);
67
+ }
68
+ emit(type, args) {
69
+ return super.emit(type, args);
70
+ }
71
+ get sipDomain() {
72
+ return this.options.sipDomain;
73
+ }
74
+ get sipOptions() {
75
+ const options = Object.assign(Object.assign({}, this.options.sipOptions), { mediaConstraints: this.getUserMediaConstraints });
76
+ return options;
77
+ }
78
+ get currentActiveRoomId() {
79
+ return this._currentActiveRoomId;
80
+ }
81
+ set currentActiveRoomId(roomId) {
82
+ this._currentActiveRoomId = roomId;
83
+ this.emit('currentActiveRoomChanged', roomId);
84
+ }
85
+ get callAddingInProgress() {
86
+ return this._callAddingInProgress;
87
+ }
88
+ set callAddingInProgress(value) {
89
+ this._callAddingInProgress = value;
90
+ this.emit('callAddingInProgressChanged', value);
91
+ }
92
+ get muteWhenJoin() {
93
+ return this.state.muteWhenJoin;
94
+ }
95
+ set muteWhenJoin(value) {
96
+ this.state.muteWhenJoin = value;
97
+ this.emit('changeMuteWhenJoin', value);
98
+ }
99
+ get isDND() {
100
+ return this.state.isDND;
101
+ }
102
+ set isDND(value) {
103
+ this.state.isDND = value;
104
+ this.emit('changeIsDND', value);
105
+ }
106
+ get speakerVolume() {
107
+ return this.state.speakerVolume;
108
+ }
109
+ set speakerVolume(value) {
110
+ this.state.speakerVolume = value;
111
+ Object.values(activeCalls).forEach((call) => {
112
+ if (call.audioTag) {
113
+ call.audioTag.volume = value;
114
+ }
115
+ });
116
+ }
117
+ get microphoneInputLevel() {
118
+ return this.state.microphoneInputLevel;
119
+ }
120
+ set microphoneInputLevel(value) {
121
+ this.state.microphoneInputLevel = value;
122
+ this.roomReconfigure(this.currentActiveRoomId);
123
+ }
124
+ get getActiveCalls() {
125
+ return this.state.activeCalls;
126
+ }
127
+ get getActiveRooms() {
128
+ return this.state.activeRooms;
129
+ }
130
+ get isMuted() {
131
+ return this.state.isMuted;
132
+ }
133
+ set isMuted(value) {
134
+ this.state.isMuted = value;
135
+ this.emit('changeIsMuted', value);
136
+ }
137
+ get getInputDeviceList() {
138
+ return this.state.availableMediaDevices.filter(device => device.kind === 'audioinput');
139
+ }
140
+ get getOutputDeviceList() {
141
+ return this.state.availableMediaDevices.filter(device => device.kind === 'audiooutput');
142
+ }
143
+ /*getInputDeviceList: (state) => {
144
+ return state.availableMediaDevices.filter(device => device.kind === 'audioinput');
145
+ },
146
+ getOutputDeviceList: (state) => {
147
+ return state.availableMediaDevices.filter(device => device.kind === 'audiooutput');
148
+ }*/
149
+ get getUserMediaConstraints() {
150
+ return {
151
+ audio: {
152
+ deviceId: {
153
+ exact: this.state.selectedMediaDevices.input
154
+ }
155
+ },
156
+ video: false
157
+ };
158
+ }
159
+ get getInputDefaultDevice() {
160
+ return this.getInputDeviceList.find(device => device.deviceId === 'default');
161
+ }
162
+ get getOutputDefaultDevice() {
163
+ return this.getOutputDeviceList.find(device => device.deviceId === 'default');
164
+ }
165
+ get selectedInputDevice() {
166
+ return this.state.selectedMediaDevices.input;
167
+ }
168
+ set selectedInputDevice(deviceId) {
169
+ localStorage.setItem(STORAGE_KEYS.SELECTED_INPUT_DEVICE, deviceId);
170
+ this.state.selectedMediaDevices.input = deviceId;
171
+ this.emit('changeActiveInputMediaDevice', deviceId);
172
+ }
173
+ get selectedOutputDevice() {
174
+ return this.state.selectedMediaDevices.output;
175
+ }
176
+ set selectedOutputDevice(deviceId) {
177
+ localStorage.setItem(STORAGE_KEYS.SELECTED_OUTPUT_DEVICE, deviceId);
178
+ this.state.selectedMediaDevices.output = deviceId;
179
+ this.emit('changeActiveOutputMediaDevice', deviceId);
180
+ }
181
+ get originalStream() {
182
+ return this.state.originalStream;
183
+ }
184
+ setAvailableMediaDevices(devices) {
185
+ this.state.availableMediaDevices = devices;
186
+ this.emit('changeAvailableDeviceList', devices);
187
+ }
188
+ updateDeviceList() {
189
+ return __awaiter(this, void 0, void 0, function* () {
190
+ yield navigator.mediaDevices.getUserMedia(this.getUserMediaConstraints);
191
+ const devices = yield navigator.mediaDevices.enumerateDevices();
192
+ this.setAvailableMediaDevices(devices);
193
+ });
194
+ }
195
+ setMediaDevices(setDefaults = false) {
196
+ var _a, _b;
197
+ return __awaiter(this, void 0, void 0, function* () {
198
+ this.state.selectedMediaDevices.input = localStorage.getItem(STORAGE_KEYS.SELECTED_INPUT_DEVICE) || 'default';
199
+ this.state.selectedMediaDevices.output = localStorage.getItem(STORAGE_KEYS.SELECTED_OUTPUT_DEVICE) || 'default';
200
+ yield navigator.mediaDevices.getUserMedia(this.getUserMediaConstraints);
201
+ const devices = yield navigator.mediaDevices.enumerateDevices();
202
+ this.setAvailableMediaDevices(devices);
203
+ const defaultMicrophone = setDefaults
204
+ ? ((_a = this.getInputDefaultDevice) === null || _a === void 0 ? void 0 : _a.deviceId) || ''
205
+ : '';
206
+ const defaultSpeaker = setDefaults
207
+ ? ((_b = this.getOutputDefaultDevice) === null || _b === void 0 ? void 0 : _b.deviceId) || ''
208
+ : '';
209
+ yield this.setMicrophone(defaultMicrophone);
210
+ yield this.setSpeaker(defaultSpeaker);
211
+ });
212
+ }
213
+ setCallTime(value) {
214
+ const time = Object.assign({}, value);
215
+ delete time.callId;
216
+ this.state.callTime = Object.assign(Object.assign({}, this.state.callTime), { [value.callId]: time });
217
+ }
218
+ removeCallTime(callId) {
219
+ const callTimeCopy = Object.assign({}, this.state.callTime);
220
+ delete callTimeCopy[callId];
221
+ this.state.callTime = Object.assign({}, callTimeCopy);
222
+ }
223
+ setTimeInterval(callId, interval) {
224
+ this.state.timeIntervals = Object.assign(Object.assign({}, this.state.timeIntervals), { [callId]: interval });
225
+ }
226
+ removeTimeInterval(callId) {
227
+ const timeIntervalsCopy = Object.assign({}, this.state.timeIntervals);
228
+ clearInterval(timeIntervalsCopy[callId]);
229
+ delete timeIntervalsCopy[callId];
230
+ this.state.timeIntervals = Object.assign({}, timeIntervalsCopy);
231
+ }
232
+ _stopCallTimer(callId) {
233
+ this.removeTimeInterval(callId);
234
+ this.removeCallTime(callId);
235
+ }
236
+ setMetricsConfig(config) {
237
+ this.state.metricConfig = Object.assign(Object.assign({}, this.state.metricConfig), config);
238
+ }
239
+ sendDTMF(callId, value) {
240
+ const validation_regex = /^[A-D0-9]+$/g;
241
+ if (!validation_regex.test(value)) {
242
+ throw new Error('Not allowed character in DTMF input');
243
+ }
244
+ const call = activeCalls[callId];
245
+ call.sendDTMF(value);
246
+ }
247
+ doMute(value) {
248
+ const activeRoomId = this.currentActiveRoomId;
249
+ this.isMuted = value;
250
+ this.roomReconfigure(activeRoomId);
251
+ }
252
+ sendMessage(target, body, options) {
253
+ return super.sendMessage(`sip:${target}@${this.sipDomain}`, body, options);
254
+ }
255
+ doCallHold({ callId, toHold, automatic }) {
256
+ const call = activeCalls[callId];
257
+ call._automaticHold = automatic !== null && automatic !== void 0 ? automatic : false;
258
+ if (toHold) {
259
+ call.hold();
260
+ }
261
+ else {
262
+ call.unhold();
263
+ }
264
+ }
265
+ _cancelAllOutgoingUnanswered() {
266
+ Object.values(this.getActiveCalls).filter(call => {
267
+ return call.direction === 'outgoing'
268
+ && call.status === CALL_STATUS_UNANSWERED;
269
+ }).forEach(call => this.callTerminate(call._id));
270
+ }
271
+ callAnswer(callId) {
272
+ const call = activeCalls[callId];
273
+ this._cancelAllOutgoingUnanswered();
274
+ call.answer(this.sipOptions);
275
+ this.updateCall(call);
276
+ // TODO: maybe would be better to move to the top
277
+ this.setCurrentActiveRoomId(call.roomId);
278
+ call.connection.addEventListener('addstream', (event) => __awaiter(this, void 0, void 0, function* () {
279
+ this._triggerAddStream(event, call);
280
+ }));
281
+ }
282
+ callMove(callId, roomId) {
283
+ return __awaiter(this, void 0, void 0, function* () {
284
+ this._updateCallStatus({ callId, isMoving: true });
285
+ yield this.callChangeRoom({ callId, roomId });
286
+ this._updateCallStatus({ callId, isMoving: false });
287
+ });
288
+ }
289
+ updateCall(value) {
290
+ this.state.activeCalls[value._id] = (0, audio_helper_1.simplifyCallObject)(value);
291
+ this.emit('changeActiveCalls', this.state.activeCalls);
292
+ }
293
+ updateRoom(value) {
294
+ const room = this.state.activeRooms[value.roomId];
295
+ const newRoomData = Object.assign(Object.assign({}, room), value);
296
+ this.state.activeRooms = Object.assign(Object.assign({}, this.state.activeRooms), { [value.roomId]: Object.assign({}, newRoomData) });
297
+ this.emit('updateRoom', { room: newRoomData, roomList: this.state.activeRooms });
298
+ }
299
+ _addCall(value) {
300
+ this.state.activeCalls = Object.assign(Object.assign({}, this.state.activeCalls), { [value._id]: (0, audio_helper_1.simplifyCallObject)(value) });
301
+ activeCalls[value._id] = value;
302
+ this.emit('changeActiveCalls', this.state.activeCalls);
303
+ }
304
+ _addCallStatus(callId) {
305
+ this.state.callStatus = Object.assign(Object.assign({}, this.state.callStatus), { [callId]: {
306
+ isMoving: false,
307
+ isTransferring: false,
308
+ isMerging: false
309
+ } });
310
+ }
311
+ _updateCallStatus(value) {
312
+ const prevStatus = Object.assign({}, this.state.callStatus[value.callId]);
313
+ const newStatus = Object.assign({}, prevStatus);
314
+ if (value.isMoving !== undefined) {
315
+ newStatus.isMoving = value.isMoving;
316
+ }
317
+ if (value.isTransferring !== undefined) {
318
+ newStatus.isTransferring = value.isTransferring;
319
+ }
320
+ if (value.isMerging !== undefined) {
321
+ newStatus.isMerging = value.isMerging;
322
+ }
323
+ this.state.callStatus = Object.assign(Object.assign({}, this.state.callStatus), { [value.callId]: Object.assign({}, newStatus) });
324
+ }
325
+ _removeCallStatus(callId) {
326
+ const callStatusCopy = Object.assign({}, this.state.callStatus);
327
+ delete callStatusCopy[callId];
328
+ this.state.callStatus = Object.assign({}, callStatusCopy);
329
+ }
330
+ _addRoom(value) {
331
+ this.state.activeRooms = Object.assign(Object.assign({}, this.state.activeRooms), { [value.roomId]: value });
332
+ this.emit('addRoom', { room: value, roomList: this.state.activeRooms });
333
+ }
334
+ setMicrophone(dId) {
335
+ return __awaiter(this, void 0, void 0, function* () {
336
+ if (!this.getInputDeviceList.find(({ deviceId }) => deviceId === dId)) {
337
+ return;
338
+ }
339
+ this.selectedInputDevice = dId;
340
+ let stream; // = null
341
+ try {
342
+ stream = yield navigator.mediaDevices.getUserMedia(this.getUserMediaConstraints);
343
+ }
344
+ catch (err) {
345
+ console.error(err);
346
+ }
347
+ if (Object.keys(this.getActiveCalls).length === 0) {
348
+ return;
349
+ }
350
+ const callsInCurrentRoom = Object.values(activeCalls).filter(call => call.roomId === this.currentActiveRoomId);
351
+ if (callsInCurrentRoom.length === 1) {
352
+ Object.values(callsInCurrentRoom).forEach(call => {
353
+ const processedStream = (0, audio_helper_1.processAudioVolume)(stream, this.microphoneInputLevel);
354
+ processedStream.getTracks().forEach(track => track.enabled = !this.isMuted);
355
+ this._setOriginalStream(processedStream);
356
+ call.connection.getSenders()[0].replaceTrack(processedStream.getTracks()[0]);
357
+ this.updateCall(call);
358
+ });
359
+ }
360
+ else {
361
+ yield this._doConference(callsInCurrentRoom);
362
+ }
363
+ });
364
+ }
365
+ _setOriginalStream(value) {
366
+ this.state.originalStream = value;
367
+ this.emit('changeOriginalStream', value);
368
+ }
369
+ setSpeaker(dId) {
370
+ return __awaiter(this, void 0, void 0, function* () {
371
+ if (!this.getOutputDeviceList.find(({ deviceId }) => deviceId === dId)) {
372
+ return;
373
+ }
374
+ this.selectedOutputDevice = dId;
375
+ const activeCallList = Object.values(activeCalls);
376
+ if (activeCallList.length === 0) {
377
+ return;
378
+ }
379
+ const callsInCurrentRoom = activeCallList.filter(call => call.roomId === this.currentActiveRoomId);
380
+ if (callsInCurrentRoom.length === 1) {
381
+ activeCallList.forEach(call => {
382
+ var _a;
383
+ (_a = call.audioTag) === null || _a === void 0 ? void 0 : _a.setSinkId(dId);
384
+ this.updateCall(call);
385
+ });
386
+ }
387
+ else {
388
+ yield this._doConference(callsInCurrentRoom);
389
+ }
390
+ });
391
+ }
392
+ removeRoom(roomId) {
393
+ const activeRoomsCopy = Object.assign({}, this.state.activeRooms);
394
+ const roomToRemove = Object.assign({}, activeRoomsCopy[roomId]);
395
+ delete activeRoomsCopy[roomId];
396
+ this.state.activeRooms = Object.assign({}, activeRoomsCopy);
397
+ this.emit('removeRoom', { room: roomToRemove, roomList: this.state.activeRooms });
398
+ }
399
+ deleteRoomIfEmpty(roomId) {
400
+ if (roomId === undefined) {
401
+ return;
402
+ }
403
+ if (Object.values(activeCalls).filter(call => call.roomId === roomId).length === 0) {
404
+ this.removeRoom(roomId);
405
+ if (this.currentActiveRoomId === roomId) {
406
+ this.currentActiveRoomId = roomId;
407
+ }
408
+ }
409
+ }
410
+ checkInitialized() {
411
+ if (!this.initialized) {
412
+ throw new Error('[OpenSIPSJS] You must call `start` method first!');
413
+ }
414
+ }
415
+ muteReconfigure(call) {
416
+ if (this.state.isMuted) {
417
+ call.mute({ audio: true });
418
+ }
419
+ else {
420
+ call.unmute({ audio: true });
421
+ }
422
+ }
423
+ roomReconfigure(roomId) {
424
+ return __awaiter(this, void 0, void 0, function* () {
425
+ if (roomId === undefined) {
426
+ return;
427
+ }
428
+ const callsInRoom = Object.values(activeCalls).filter(call => call.roomId === roomId);
429
+ // Let`s take care on the audio output first and check if passed room is our selected room
430
+ if (this.currentActiveRoomId === roomId) {
431
+ callsInRoom.forEach(call => {
432
+ if (call.audioTag) {
433
+ this.muteReconfigure(call);
434
+ call.audioTag.muted = false;
435
+ this.updateCall(call);
436
+ }
437
+ });
438
+ }
439
+ else {
440
+ callsInRoom.forEach(call => {
441
+ if (call.audioTag) {
442
+ call.audioTag.muted = true;
443
+ this.updateCall(call);
444
+ }
445
+ });
446
+ }
447
+ // Now let`s configure the sound we are sending for each active call on this room
448
+ if (callsInRoom.length === 0) {
449
+ this.deleteRoomIfEmpty(roomId);
450
+ }
451
+ else if (callsInRoom.length === 1 && this.currentActiveRoomId !== roomId) {
452
+ if (!callsInRoom[0].isOnHold().local) {
453
+ this.doCallHold({ callId: callsInRoom[0].id, toHold: true, automatic: true });
454
+ }
455
+ }
456
+ else if (callsInRoom.length === 1 && this.currentActiveRoomId === roomId) {
457
+ if (callsInRoom[0].isOnHold().local && callsInRoom[0]._automaticHold) {
458
+ this.doCallHold({ callId: callsInRoom[0].id, toHold: false });
459
+ }
460
+ let stream;
461
+ try {
462
+ stream = yield navigator.mediaDevices.getUserMedia(this.getUserMediaConstraints);
463
+ }
464
+ catch (err) {
465
+ console.error(err);
466
+ }
467
+ if (stream && callsInRoom[0].connection && callsInRoom[0].connection.getSenders()[0]) {
468
+ const processedStream = (0, audio_helper_1.processAudioVolume)(stream, this.microphoneInputLevel);
469
+ processedStream.getTracks().forEach(track => track.enabled = !this.state.isMuted);
470
+ this._setOriginalStream(processedStream);
471
+ yield callsInRoom[0].connection.getSenders()[0].replaceTrack(processedStream.getTracks()[0]);
472
+ this.muteReconfigure(callsInRoom[0]);
473
+ }
474
+ }
475
+ else if (callsInRoom.length > 1) {
476
+ yield this._doConference(callsInRoom);
477
+ }
478
+ });
479
+ }
480
+ _doConference(sessions) {
481
+ return __awaiter(this, void 0, void 0, function* () {
482
+ sessions.forEach(call => {
483
+ if (call._localHold) {
484
+ this.doCallHold({ callId: call._id, toHold: false });
485
+ }
486
+ });
487
+ // Take all received tracks from the sessions you want to merge
488
+ const receivedTracks = [];
489
+ sessions.forEach(session => {
490
+ if (session !== null && session !== undefined) {
491
+ session.connection.getReceivers().forEach((receiver) => {
492
+ receivedTracks.push(receiver.track);
493
+ });
494
+ }
495
+ });
496
+ // Use the Web Audio API to mix the received tracks
497
+ const audioContext = new AudioContext();
498
+ const allReceivedMediaStreams = new MediaStream();
499
+ // For each call we will build dedicated mix for all other calls
500
+ yield (0, p_iteration_1.forEach)(sessions, (session) => __awaiter(this, void 0, void 0, function* () {
501
+ if (session === null || session === undefined) {
502
+ return;
503
+ }
504
+ const mixedOutput = audioContext.createMediaStreamDestination();
505
+ session.connection.getReceivers().forEach((receiver) => {
506
+ receivedTracks.forEach(track => {
507
+ allReceivedMediaStreams.addTrack(receiver.track);
508
+ if (receiver.track.id !== track.id) {
509
+ const sourceStream = audioContext.createMediaStreamSource(new MediaStream([track]));
510
+ sourceStream.connect(mixedOutput);
511
+ }
512
+ });
513
+ });
514
+ if (sessions[0].roomId === this.currentActiveRoomId) {
515
+ // Mixing your voice with all the received audio
516
+ const stream = yield navigator.mediaDevices.getUserMedia(this.getUserMediaConstraints);
517
+ const processedStream = (0, audio_helper_1.processAudioVolume)(stream, this.microphoneInputLevel);
518
+ processedStream.getTracks().forEach(track => track.enabled = !this.isMuted);
519
+ this._setOriginalStream(processedStream);
520
+ const sourceStream = audioContext.createMediaStreamSource(processedStream);
521
+ // stream.getTracks().forEach(track => track.enabled = !getters.isMuted) // TODO: Fix this
522
+ sourceStream.connect(mixedOutput);
523
+ }
524
+ if (session.connection.getSenders()[0]) {
525
+ //mixedOutput.stream.getTracks().forEach(track => track.enabled = !getters.isMuted) // Uncomment to mute all callers on mute
526
+ yield session.connection.getSenders()[0].replaceTrack(mixedOutput.stream.getTracks()[0]);
527
+ this._muteReconfigure(session);
528
+ }
529
+ }));
530
+ });
531
+ }
532
+ _muteReconfigure(call) {
533
+ if (this.isMuted) {
534
+ call.mute({ audio: true });
535
+ }
536
+ else {
537
+ call.unmute({ audio: true });
538
+ }
539
+ }
540
+ muteCaller(callId, value) {
541
+ const call = activeCalls[callId];
542
+ if (call && call.connection.getReceivers().length) {
543
+ call.localMuted = value;
544
+ call.connection.getReceivers().forEach((receiver) => {
545
+ receiver.track.enabled = !value;
546
+ });
547
+ this.updateCall(call);
548
+ this.roomReconfigure(call.roomId);
549
+ }
550
+ }
551
+ callTerminate(callId) {
552
+ const call = activeCalls[callId];
553
+ if (call._status !== 8) {
554
+ call.terminate();
555
+ }
556
+ }
557
+ callTransfer(callId, target) {
558
+ if (target.toString().length === 0) {
559
+ return console.error('Target must be passed');
560
+ }
561
+ this._updateCallStatus({ callId, isTransferring: true });
562
+ const call = activeCalls[callId];
563
+ call.refer(`sip:${target}@${this.sipDomain}`);
564
+ this.updateCall(call);
565
+ }
566
+ callMerge(roomId) {
567
+ const callsInRoom = Object.values(activeCalls).filter((call) => call.roomId === roomId);
568
+ if (callsInRoom.length !== 2)
569
+ return;
570
+ const firstCall = callsInRoom[0];
571
+ const secondCall = callsInRoom[1];
572
+ if (!firstCall || !secondCall) {
573
+ return;
574
+ }
575
+ // TODO: Check all call.id for working in the same way as call._id
576
+ this._updateCallStatus({ callId: firstCall._id, isMerging: true });
577
+ this._updateCallStatus({ callId: secondCall._id, isMerging: true });
578
+ firstCall.refer(secondCall.remote_identity.uri.toString(), { 'replaces': secondCall });
579
+ this.updateCall(firstCall);
580
+ }
581
+ // TODO: Use this method in demo
582
+ setDND(value) {
583
+ this.isDND = value;
584
+ }
585
+ _startCallTimer(callId) {
586
+ const timeData = {
587
+ callId,
588
+ hours: 0,
589
+ minutes: 0,
590
+ seconds: 0,
591
+ formatted: ''
592
+ };
593
+ this.setCallTime(timeData);
594
+ const interval = setInterval(() => {
595
+ const callTime = Object.assign({}, this.state.callTime[callId]);
596
+ const updatedTime = (0, time_helper_1.setupTime)(callTime);
597
+ this.setCallTime(Object.assign({ callId }, updatedTime));
598
+ }, 1000);
599
+ this.setTimeInterval(callId, interval);
600
+ }
601
+ setCurrentActiveRoomId(roomId) {
602
+ return __awaiter(this, void 0, void 0, function* () {
603
+ const oldRoomId = this.currentActiveRoomId;
604
+ if (roomId === oldRoomId) {
605
+ return;
606
+ }
607
+ this.currentActiveRoomId = roomId;
608
+ yield this.roomReconfigure(oldRoomId);
609
+ yield this.roomReconfigure(roomId);
610
+ });
611
+ }
612
+ getNewRoomId() {
613
+ const roomIdList = Object.keys(this.state.activeRooms);
614
+ if (roomIdList.length === 0) {
615
+ return 1;
616
+ }
617
+ return (parseInt(roomIdList.sort()[roomIdList.length - 1]) + 1);
618
+ }
619
+ subscribe(type, listener) {
620
+ const isListenerEmpty = !this.state.listeners[type] || !this.state.listeners[type].length;
621
+ const newListeners = isListenerEmpty ? [listener] : [...this.state.listeners[type], listener];
622
+ this.state.listeners = Object.assign(Object.assign({}, this.state.listeners), { [type]: newListeners });
623
+ }
624
+ removeIListener(value) {
625
+ const listenersCopy = Object.assign({}, this.state.listeners);
626
+ delete listenersCopy[value];
627
+ this.state.listeners = Object.assign({}, listenersCopy);
628
+ }
629
+ addCall(session) {
630
+ return __awaiter(this, void 0, void 0, function* () {
631
+ const sessionAlreadyInActiveCalls = this.getActiveCalls[session.id];
632
+ if (sessionAlreadyInActiveCalls !== undefined) {
633
+ return;
634
+ }
635
+ const roomId = this.getNewRoomId();
636
+ const newRoomInfo = {
637
+ started: new Date(),
638
+ incomingInProgress: false,
639
+ roomId
640
+ };
641
+ if (session.direction === 'incoming') {
642
+ newRoomInfo.incomingInProgress = true;
643
+ this.subscribe(call_event_listener_type_1.CALL_EVENT_LISTENER_TYPE.CALL_CONFIRMED, (call) => {
644
+ if (session.id === call.id) {
645
+ this.updateRoom({
646
+ incomingInProgress: false,
647
+ roomId
648
+ });
649
+ this._startCallTimer(session.id);
650
+ }
651
+ });
652
+ this.subscribe(call_event_listener_type_1.CALL_EVENT_LISTENER_TYPE.CALL_FAILED, (call) => {
653
+ if (session.id === call.id) {
654
+ this.updateRoom({
655
+ incomingInProgress: false,
656
+ roomId
657
+ });
658
+ }
659
+ });
660
+ }
661
+ else if (session.direction === 'outgoing') {
662
+ this._startCallTimer(session.id);
663
+ }
664
+ const call = session;
665
+ call.roomId = roomId;
666
+ call.localMuted = false;
667
+ this._addCall(call);
668
+ this._addCallStatus(session.id);
669
+ this._addRoom(newRoomInfo);
670
+ });
671
+ }
672
+ _triggerListener({ listenerType, session, event }) {
673
+ const listeners = this.state.listeners[listenerType];
674
+ if (!listeners || !listeners.length) {
675
+ return;
676
+ }
677
+ listeners.forEach((listener) => {
678
+ listener(session, event);
679
+ });
680
+ }
681
+ _removeCall(value) {
682
+ const stateActiveCallsCopy = Object.assign({}, this.state.activeCalls);
683
+ delete stateActiveCallsCopy[value];
684
+ delete activeCalls[value];
685
+ this.state.activeCalls = Object.assign({}, stateActiveCallsCopy);
686
+ this.emit('changeActiveCalls', this.state.activeCalls);
687
+ }
688
+ _activeCallListRemove(call) {
689
+ const callRoomIdToConfigure = activeCalls[call._id].roomId;
690
+ this._removeCall(call._id);
691
+ this.roomReconfigure(callRoomIdToConfigure);
692
+ }
693
+ newRTCSessionCallback(event) {
694
+ const session = event.session;
695
+ if (this.isDND) {
696
+ session.terminate({ status_code: 486, reason_phrase: 'Do Not Disturb' });
697
+ return;
698
+ }
699
+ // stop timers on ended and failed
700
+ session.on('ended', (event) => {
701
+ this._triggerListener({ listenerType: call_event_listener_type_1.CALL_EVENT_LISTENER_TYPE.CALL_ENDED, session, event });
702
+ const s = this.getActiveCalls[session.id];
703
+ this._activeCallListRemove(s);
704
+ this._stopCallTimer(session.id);
705
+ this._removeCallStatus(session.id);
706
+ this._removeCallMetrics(session.id);
707
+ if (!Object.keys(activeCalls).length) {
708
+ this.isMuted = false;
709
+ }
710
+ });
711
+ session.on('progress', (event) => {
712
+ this._triggerListener({ listenerType: call_event_listener_type_1.CALL_EVENT_LISTENER_TYPE.CALL_PROGRESS, session, event });
713
+ });
714
+ session.on('failed', (event) => {
715
+ this._triggerListener({ listenerType: call_event_listener_type_1.CALL_EVENT_LISTENER_TYPE.CALL_FAILED, session, event });
716
+ if (session.id === this.callAddingInProgress) {
717
+ this.callAddingInProgress = undefined;
718
+ }
719
+ const s = this.getActiveCalls[session.id];
720
+ this._activeCallListRemove(s);
721
+ this._stopCallTimer(session.id);
722
+ this._removeCallStatus(session.id);
723
+ this._removeCallMetrics(session.id);
724
+ if (!Object.keys(activeCalls).length) {
725
+ this.isMuted = false;
726
+ }
727
+ });
728
+ session.on('confirmed', (event) => {
729
+ this._triggerListener({ listenerType: call_event_listener_type_1.CALL_EVENT_LISTENER_TYPE.CALL_CONFIRMED, session, event });
730
+ this.updateCall(session);
731
+ if (session.id === this.callAddingInProgress) {
732
+ this.callAddingInProgress = undefined;
733
+ }
734
+ });
735
+ this.addCall(session);
736
+ if (session.direction === 'outgoing') {
737
+ const roomId = this.getActiveCalls[session.id].roomId;
738
+ this.setCurrentActiveRoomId(roomId);
739
+ }
740
+ }
741
+ setInitialized() {
742
+ this.initialized = true;
743
+ this.emit('ready', true);
744
+ }
745
+ start() {
746
+ this.on(this.newRTCSessionEventName, this.newRTCSessionCallback.bind(this));
747
+ super.start();
748
+ this.setInitialized();
749
+ //this.setDefaultMediaDevices()
750
+ this.setMediaDevices(true);
751
+ return this;
752
+ }
753
+ setMuteWhenJoin(value) {
754
+ this.muteWhenJoin = value;
755
+ }
756
+ _setCallMetrics(value) {
757
+ const metrics = Object.assign({}, value);
758
+ delete metrics['callId'];
759
+ this.state.callMetrics = Object.assign(Object.assign({}, this.state.callMetrics), { [value.callId]: metrics });
760
+ }
761
+ _removeCallMetrics(callId) {
762
+ const callMetricsCopy = Object.assign({}, this.state.callMetrics);
763
+ delete callMetricsCopy[callId];
764
+ this.state.callMetrics = Object.assign({}, callMetricsCopy);
765
+ }
766
+ _getCallQuality(call) {
767
+ const metrics = new metrics_1.default(this.state.metricConfig);
768
+ const probe = metrics.createProbe(call.connection, {
769
+ cid: call._id
770
+ });
771
+ const inboundKeys = [];
772
+ let inboundAudio;
773
+ probe.onreport = (probe) => {
774
+ //console.log('probe', probe)
775
+ /*const inboundMetrics = Object.entries(probe.audio).filter(([ key, value ]) => {
776
+ return value.direction === 'inbound'
777
+ })*/
778
+ //const ioo = Object.entries(probe.audio).
779
+ Object.entries(probe.audio).forEach(([key, value]) => {
780
+ if (value.direction === 'inbound' && !inboundKeys.includes(key)) {
781
+ inboundKeys.push(key);
782
+ inboundAudio = key;
783
+ }
784
+ });
785
+ /*inboundMetrics.forEach(([ key, value ]) => {
786
+ if (!inboundKeys.includes(key)) {
787
+ inboundKeys.push(key)
788
+ inboundAudio = key
789
+ }
790
+ })*/
791
+ const inboundAudioMetric = probe.audio[inboundAudio];
792
+ const metric = (0, filter_helper_1.filterObjectKeys)(inboundAudioMetric, metric_keys_to_include_1.METRIC_KEYS_TO_INCLUDE);
793
+ metric.callId = call._id;
794
+ this._setCallMetrics(metrics);
795
+ };
796
+ this.subscribe(call_event_listener_type_1.CALL_EVENT_LISTENER_TYPE.CALL_ENDED, (session) => {
797
+ if (session._id === call._id) {
798
+ metrics.stopAllProbes();
799
+ }
800
+ });
801
+ metrics.startAllProbes();
802
+ }
803
+ _triggerAddStream(event, call) {
804
+ return __awaiter(this, void 0, void 0, function* () {
805
+ this.isMuted = this.muteWhenJoin;
806
+ const stream = yield navigator.mediaDevices.getUserMedia(this.getUserMediaConstraints);
807
+ const processedStream = (0, audio_helper_1.processAudioVolume)(stream, this.microphoneInputLevel);
808
+ const muteMicro = this.isMuted || this.muteWhenJoin;
809
+ processedStream.getTracks().forEach(track => track.enabled = !muteMicro);
810
+ this._setOriginalStream(processedStream);
811
+ yield call.connection.getSenders()[0].replaceTrack(processedStream.getTracks()[0]);
812
+ (0, audio_helper_1.syncStream)(event, call, this.selectedOutputDevice, this.speakerVolume);
813
+ this._getCallQuality(call);
814
+ this.updateCall(call);
815
+ });
816
+ }
817
+ doCall({ target, addToCurrentRoom }) {
818
+ this.checkInitialized();
819
+ if (target.length === 0) {
820
+ return console.error('Target must be a valid string');
821
+ }
822
+ const call = this.call(`sip:${target}@${this.sipDomain}`, this.sipOptions);
823
+ this.callAddingInProgress = call.id;
824
+ if (addToCurrentRoom && this.currentActiveRoomId !== undefined) {
825
+ this.callChangeRoom({
826
+ callId: call.id,
827
+ roomId: this.currentActiveRoomId
828
+ });
829
+ }
830
+ call.connection.addEventListener('addstream', (event) => {
831
+ this._triggerAddStream(event, call);
832
+ });
833
+ }
834
+ callChangeRoom({ callId, roomId }) {
835
+ return __awaiter(this, void 0, void 0, function* () {
836
+ const oldRoomId = activeCalls[callId].roomId;
837
+ activeCalls[callId].roomId = roomId;
838
+ yield this.setCurrentActiveRoomId(roomId);
839
+ return Promise.all([
840
+ this.roomReconfigure(oldRoomId),
841
+ this.roomReconfigure(roomId)
842
+ ]).then(() => {
843
+ this.deleteRoomIfEmpty(oldRoomId);
844
+ this.deleteRoomIfEmpty(roomId);
845
+ });
846
+ });
847
+ }
848
+ }
849
+ exports.default = OpenSIPSJS;