@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.
- package/README.md +75 -0
- package/build/enum/call.event.listener.type.d.ts +7 -0
- package/build/enum/call.event.listener.type.js +10 -0
- package/build/enum/metric.keys.to.include.d.ts +2 -0
- package/build/enum/metric.keys.to.include.js +4 -0
- package/build/helpers/UA/index.d.ts +6 -0
- package/build/helpers/UA/index.js +9 -0
- package/build/helpers/audio.helper.d.ts +9 -0
- package/build/helpers/audio.helper.js +60 -0
- package/build/helpers/filter.helper.d.ts +2 -0
- package/build/helpers/filter.helper.js +14 -0
- package/build/helpers/time.helper.d.ts +16 -0
- package/build/helpers/time.helper.js +28 -0
- package/build/helpers/volume.helper.d.ts +2 -0
- package/build/helpers/volume.helper.js +76 -0
- package/build/helpers/webrtcmetrics/collector.d.ts +32 -0
- package/build/helpers/webrtcmetrics/collector.js +282 -0
- package/build/helpers/webrtcmetrics/engine.d.ts +20 -0
- package/build/helpers/webrtcmetrics/engine.js +164 -0
- package/build/helpers/webrtcmetrics/exporter.d.ts +116 -0
- package/build/helpers/webrtcmetrics/exporter.js +528 -0
- package/build/helpers/webrtcmetrics/extractor.d.ts +1 -0
- package/build/helpers/webrtcmetrics/extractor.js +976 -0
- package/build/helpers/webrtcmetrics/index.d.ts +63 -0
- package/build/helpers/webrtcmetrics/index.js +93 -0
- package/build/helpers/webrtcmetrics/metrics.d.ts +2 -0
- package/build/helpers/webrtcmetrics/metrics.js +8 -0
- package/build/helpers/webrtcmetrics/probe.d.ts +76 -0
- package/build/helpers/webrtcmetrics/probe.js +153 -0
- package/build/helpers/webrtcmetrics/utils/config.d.ts +12 -0
- package/build/helpers/webrtcmetrics/utils/config.js +28 -0
- package/build/helpers/webrtcmetrics/utils/helper.d.ts +13 -0
- package/build/helpers/webrtcmetrics/utils/helper.js +134 -0
- package/build/helpers/webrtcmetrics/utils/log.d.ts +7 -0
- package/build/helpers/webrtcmetrics/utils/log.js +71 -0
- package/build/helpers/webrtcmetrics/utils/models.d.ts +309 -0
- package/build/helpers/webrtcmetrics/utils/models.js +298 -0
- package/build/helpers/webrtcmetrics/utils/score.d.ts +4 -0
- package/build/helpers/webrtcmetrics/utils/score.js +235 -0
- package/build/helpers/webrtcmetrics/utils/shortUUId.d.ts +1 -0
- package/build/helpers/webrtcmetrics/utils/shortUUId.js +7 -0
- package/build/index.d.ts +170 -0
- package/build/index.js +849 -0
- package/package.json +61 -0
- package/src/types/declarations.d.ts +6 -0
- package/src/types/generic.d.ts +1 -0
- package/src/types/listeners.d.ts +42 -0
- package/src/types/rtc.d.ts +133 -0
- package/src/types/webrtcmetrics.d.ts +64 -0
package/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# Getting started
|
2
|
+
## Installation
|
3
|
+
Using npm:
|
4
|
+
```shell
|
5
|
+
$ npm i @voicenter-team/opensips-js
|
6
|
+
```
|
7
|
+
|
8
|
+
## Usage
|
9
|
+
Firstly lets import the library and create the OpenSIPS instance:
|
10
|
+
```javascript
|
11
|
+
import OpenSIPSJS from '@voicenter-team/opensips-js'
|
12
|
+
|
13
|
+
const openSIPSJS = new OpenSIPSJS({
|
14
|
+
configuration: {
|
15
|
+
session_timers: false,
|
16
|
+
uri: 'sip:extension_user@domain',
|
17
|
+
password: 'password',
|
18
|
+
},
|
19
|
+
socketInterfaces: [ 'wss://domain' ],
|
20
|
+
sipDomain: 'domain',
|
21
|
+
sipOptions: {
|
22
|
+
session_timers: false,
|
23
|
+
extraHeaders: [ 'X-Bar: bar' ],
|
24
|
+
pcConfig: {},
|
25
|
+
},
|
26
|
+
})
|
27
|
+
```
|
28
|
+
|
29
|
+
Then you will be able to call next methods on openSIPSJS instance:
|
30
|
+
|
31
|
+
### Methods
|
32
|
+
- `async setMediaDevices(setDefaults: Boolean = false)` - will set up media devices
|
33
|
+
- `async setMicrophone(deviceId: String)` - set passed device as input device for calls
|
34
|
+
- `async setSpeaker(deviceId: String)` - set passed device as output device for calls
|
35
|
+
- `async setCurrentActiveRoomId(roomId: Number)` - move to the room
|
36
|
+
- `doCallHold({callId: Number, toHold: Boolean, automatic: Boolean})` - hold/unhold call by id
|
37
|
+
- `doCall(target: String, addToCurrentRoom: Boolean)` - call to the target. If addToCurrentRoom is true then the call will be added to the user's current room
|
38
|
+
- `callTerminate(callId: String)` - terminate call
|
39
|
+
- `callTransfer({callId: String, target: String})` - transfer call to target
|
40
|
+
- `callMerge(roomId: Number)` - merge calls in specific room
|
41
|
+
- `callAnswer(callId: String)` - answer the call
|
42
|
+
- `setMetricsConfig(config: WebrtcMetricsConfigType)` - set the metric config (used for audio quality indicator)
|
43
|
+
- `doMute(muted: Boolean)` - set the agent muteness
|
44
|
+
- `setDND(value: Boolean)` - set the agent "Do not disturb" status
|
45
|
+
- `async callChangeRoom({callId: String, roomId: Number})` - move call to the room
|
46
|
+
- `callMove({callId: String, roomId: Number})` - Same as callChangeRoom. Move call to the specific room
|
47
|
+
- `subscribe({type: String, listener: function})` - subscribe to an event. Available events: `new_call`, `ended`, `progress`, `failed`, `confirmed`
|
48
|
+
- `removeIListener(type: String)` - remove event listener
|
49
|
+
|
50
|
+
WebrtcMetricsConfigType
|
51
|
+
|
52
|
+
| Parameter | Type |
|
53
|
+
|----------------|------------------------|
|
54
|
+
| `refreshEvery` | `number \| undefined` |
|
55
|
+
| `startAfter` | `number \| undefined` |
|
56
|
+
| `startAfter` | `number \| undefined` |
|
57
|
+
| `verbose` | `boolean \| undefined` |
|
58
|
+
| `pname` | `string \| undefined` |
|
59
|
+
| `cid` | `string \| undefined` |
|
60
|
+
| `uid` | `string \| undefined` |
|
61
|
+
| `record` | `boolean \| undefined` |
|
62
|
+
| `ticket` | `boolean \| undefined` |
|
63
|
+
|
64
|
+
Also there are next public fields on openSIPSJS instance:
|
65
|
+
### Fields
|
66
|
+
- `getActiveRooms: { [key: number]: IRoom }` - returns an object of active rooms where key is room id and value is room data
|
67
|
+
- `sipDomain: String` - returns sip domain
|
68
|
+
- `sipOptions: Object` - returns sip options
|
69
|
+
- `getInputDeviceList: []` - returns list of input devices
|
70
|
+
- `getOutputDeviceList: []` - returns list of output devices
|
71
|
+
- `currentActiveRoomId: Number` - returns current active room id
|
72
|
+
- `selectedInputDevice: String` - returns current selected input device id
|
73
|
+
- `selectedOutputDevice: String` - returns current selected output device id
|
74
|
+
- `isDND: Boolean` - returns if the agent is in "Do not disturb" status
|
75
|
+
- `isMuted: Boolean` - returns if the agent is muted
|
@@ -0,0 +1,10 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.CALL_EVENT_LISTENER_TYPE = void 0;
|
4
|
+
exports.CALL_EVENT_LISTENER_TYPE = {
|
5
|
+
NEW_CALL: 'new_call',
|
6
|
+
CALL_CONFIRMED: 'confirmed',
|
7
|
+
CALL_FAILED: 'failed',
|
8
|
+
CALL_PROGRESS: 'progress',
|
9
|
+
CALL_ENDED: 'ended'
|
10
|
+
};
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { ICall, MediaEvent } from '@/types/rtc';
|
2
|
+
import { Writeable } from '@/types/generic';
|
3
|
+
type ICallKey = keyof ICall;
|
4
|
+
declare const CALL_KEYS_TO_INCLUDE: Array<ICallKey>;
|
5
|
+
export type ICallSimplified = Writeable<Pick<ICall, typeof CALL_KEYS_TO_INCLUDE[number]>>;
|
6
|
+
export declare function simplifyCallObject(call: ICall): ICallSimplified;
|
7
|
+
export declare function processAudioVolume(stream: MediaStream, volume: number): MediaStream;
|
8
|
+
export declare function syncStream(event: MediaEvent, call: ICall, outputDevice: string, volume: number): void;
|
9
|
+
export {};
|
@@ -0,0 +1,60 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.syncStream = exports.processAudioVolume = exports.simplifyCallObject = void 0;
|
4
|
+
const CALL_KEYS_TO_INCLUDE = [
|
5
|
+
'roomId',
|
6
|
+
'_audioMuted',
|
7
|
+
'_cancel_reason',
|
8
|
+
'_contact',
|
9
|
+
'direction',
|
10
|
+
'_end_time',
|
11
|
+
'_eventsCount',
|
12
|
+
'_from_tag',
|
13
|
+
'_id',
|
14
|
+
'_is_canceled',
|
15
|
+
'_is_confirmed',
|
16
|
+
'_late_sdp',
|
17
|
+
'_localHold',
|
18
|
+
'_videoMuted',
|
19
|
+
'status',
|
20
|
+
'start_time',
|
21
|
+
'_remote_identity',
|
22
|
+
'audioTag',
|
23
|
+
//'audioQuality',
|
24
|
+
'isOnHold',
|
25
|
+
//'originalStream',
|
26
|
+
'localMuted'
|
27
|
+
];
|
28
|
+
function simplifyCallObject(call) {
|
29
|
+
const simplified = {};
|
30
|
+
CALL_KEYS_TO_INCLUDE.forEach(key => {
|
31
|
+
if (call[key] !== undefined) {
|
32
|
+
simplified[key] = call[key];
|
33
|
+
}
|
34
|
+
});
|
35
|
+
simplified.localHold = call._localHold;
|
36
|
+
return simplified;
|
37
|
+
}
|
38
|
+
exports.simplifyCallObject = simplifyCallObject;
|
39
|
+
function processAudioVolume(stream, volume) {
|
40
|
+
const audioContext = new AudioContext();
|
41
|
+
const audioSource = audioContext.createMediaStreamSource(stream);
|
42
|
+
const audioDestination = audioContext.createMediaStreamDestination();
|
43
|
+
const gainNode = audioContext.createGain();
|
44
|
+
audioSource.connect(gainNode);
|
45
|
+
gainNode.connect(audioDestination);
|
46
|
+
gainNode.gain.value = volume;
|
47
|
+
return audioDestination.stream;
|
48
|
+
}
|
49
|
+
exports.processAudioVolume = processAudioVolume;
|
50
|
+
function syncStream(event, call, outputDevice, volume) {
|
51
|
+
const audio = document.createElement('audio');
|
52
|
+
audio.id = call._id;
|
53
|
+
audio.className = 'audioTag';
|
54
|
+
audio.srcObject = event.stream;
|
55
|
+
audio.setSinkId(outputDevice);
|
56
|
+
audio.volume = volume;
|
57
|
+
audio.play();
|
58
|
+
call.audioTag = audio;
|
59
|
+
}
|
60
|
+
exports.syncStream = syncStream;
|
@@ -0,0 +1,14 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.filterObjectKeys = void 0;
|
4
|
+
function filterObjectKeys(fullObj, keys) {
|
5
|
+
return Object.keys(fullObj)
|
6
|
+
.filter((key) => keys.includes(key))
|
7
|
+
.reduce((obj, key) => {
|
8
|
+
const k = key;
|
9
|
+
//const o = obj as ProbeMetricInType
|
10
|
+
//o[k] = fullObj[k] //as ProbeMetricInType[keyof ProbeMetricInType]
|
11
|
+
return Object.assign(Object.assign({}, obj), { [k]: fullObj[k] });
|
12
|
+
}, {});
|
13
|
+
}
|
14
|
+
exports.filterObjectKeys = filterObjectKeys;
|
@@ -0,0 +1,16 @@
|
|
1
|
+
export interface ITimeData {
|
2
|
+
callId: string;
|
3
|
+
hours: number;
|
4
|
+
minutes: number;
|
5
|
+
seconds: number;
|
6
|
+
formatted: string;
|
7
|
+
}
|
8
|
+
export type TempTimeData = Omit<ITimeData, 'callId'> & {
|
9
|
+
callId: string | undefined;
|
10
|
+
};
|
11
|
+
export declare function setupTime(time: TempTimeData): {
|
12
|
+
seconds: number;
|
13
|
+
minutes: number;
|
14
|
+
hours: number;
|
15
|
+
formatted: string;
|
16
|
+
};
|
@@ -0,0 +1,28 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.setupTime = void 0;
|
4
|
+
function formatTime(time) {
|
5
|
+
return time < 10 ? `0${time}` : `${time}`;
|
6
|
+
}
|
7
|
+
function setupTime(time) {
|
8
|
+
let hours = time.hours || 0;
|
9
|
+
let minutes = time.minutes || 0;
|
10
|
+
let seconds = time.seconds || 0;
|
11
|
+
seconds++;
|
12
|
+
if (seconds === 60) {
|
13
|
+
seconds = 0;
|
14
|
+
minutes++;
|
15
|
+
if (minutes === 60) {
|
16
|
+
minutes = 0;
|
17
|
+
hours++;
|
18
|
+
}
|
19
|
+
}
|
20
|
+
const formatted = `${formatTime(hours)}:${formatTime(minutes)}:${formatTime(seconds)}`;
|
21
|
+
return {
|
22
|
+
seconds,
|
23
|
+
minutes,
|
24
|
+
hours,
|
25
|
+
formatted
|
26
|
+
};
|
27
|
+
}
|
28
|
+
exports.setupTime = setupTime;
|
@@ -0,0 +1,76 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.clearVolumeInterval = exports.runIndicator = void 0;
|
4
|
+
const height = 20;
|
5
|
+
const lineWidth = 4;
|
6
|
+
let interval = undefined;
|
7
|
+
const runIndicator = (stream, deviceId) => {
|
8
|
+
if (stream && stream.getTracks().length) {
|
9
|
+
//console.log('RUN INDICATOR IF')
|
10
|
+
getVolumeLevelBar(stream, deviceId);
|
11
|
+
}
|
12
|
+
else {
|
13
|
+
//console.log('RUN INDICATOR ELSE')
|
14
|
+
(0, exports.clearVolumeInterval)();
|
15
|
+
}
|
16
|
+
};
|
17
|
+
exports.runIndicator = runIndicator;
|
18
|
+
const clearVolumeInterval = () => {
|
19
|
+
clearInterval(interval);
|
20
|
+
};
|
21
|
+
exports.clearVolumeInterval = clearVolumeInterval;
|
22
|
+
const getMaxSmallIndicatorHeight = (value) => {
|
23
|
+
const halfLineHeight = height / 4;
|
24
|
+
return value < halfLineHeight ? value : halfLineHeight;
|
25
|
+
};
|
26
|
+
const getVolumeLevelBar = (stream, deviceId) => {
|
27
|
+
//console.log('IN GET VOLUME LEVEL BAR')
|
28
|
+
//console.log('TRACKS LENGTH', stream.getTracks().length)
|
29
|
+
clearInterval(interval);
|
30
|
+
const audioContext = new AudioContext();
|
31
|
+
const analyser = audioContext.createAnalyser();
|
32
|
+
const microphone = audioContext.createMediaStreamSource(stream);
|
33
|
+
const javascriptNode = audioContext.createScriptProcessor(2048, 1, 1);
|
34
|
+
analyser.smoothingTimeConstant = 0.8;
|
35
|
+
analyser.fftSize = 1024;
|
36
|
+
microphone.connect(analyser);
|
37
|
+
analyser.connect(javascriptNode);
|
38
|
+
javascriptNode.connect(audioContext.destination);
|
39
|
+
const canvas = document.getElementById(`canvas-${deviceId}`);
|
40
|
+
if (!canvas) {
|
41
|
+
return;
|
42
|
+
}
|
43
|
+
const indicatorWidth = lineWidth * 5;
|
44
|
+
const halfLineHeight = height / 2;
|
45
|
+
canvas.setAttribute('width', `${indicatorWidth}`);
|
46
|
+
canvas.setAttribute('height', `${height}`);
|
47
|
+
const canvasContext = canvas.getContext('2d');
|
48
|
+
interval = setInterval(() => {
|
49
|
+
if (!canvasContext) {
|
50
|
+
return;
|
51
|
+
}
|
52
|
+
const array = new Uint8Array(analyser.frequencyBinCount);
|
53
|
+
analyser.getByteFrequencyData(array);
|
54
|
+
let values = 0;
|
55
|
+
const length = array.length;
|
56
|
+
for (let i = 0; i < length; i++) {
|
57
|
+
values += (array[i]);
|
58
|
+
}
|
59
|
+
const average = values / length;
|
60
|
+
//console.log('average', average)
|
61
|
+
canvasContext.fillStyle = 'blue'; //getComputedStyle(document.body).getPropertyValue('--primary-actions')
|
62
|
+
const halfValue = average / 2;
|
63
|
+
canvasContext.clearRect(0, halfLineHeight, lineWidth, halfLineHeight);
|
64
|
+
canvasContext.fillRect(0, halfLineHeight, lineWidth, getMaxSmallIndicatorHeight(halfValue));
|
65
|
+
canvasContext.clearRect(0, halfLineHeight, lineWidth, -halfLineHeight);
|
66
|
+
canvasContext.fillRect(0, halfLineHeight, lineWidth, 0 - getMaxSmallIndicatorHeight(halfValue));
|
67
|
+
canvasContext.clearRect(lineWidth * 2, halfLineHeight, lineWidth, halfLineHeight);
|
68
|
+
canvasContext.fillRect(lineWidth * 2, halfLineHeight, lineWidth, average);
|
69
|
+
canvasContext.clearRect(lineWidth * 2, halfLineHeight, lineWidth, -halfLineHeight);
|
70
|
+
canvasContext.fillRect(lineWidth * 2, halfLineHeight, lineWidth, 0 - average);
|
71
|
+
canvasContext.clearRect(lineWidth * 4, halfLineHeight, lineWidth, halfLineHeight);
|
72
|
+
canvasContext.fillRect(lineWidth * 4, halfLineHeight, lineWidth, getMaxSmallIndicatorHeight(halfValue));
|
73
|
+
canvasContext.clearRect(lineWidth * 4, halfLineHeight, lineWidth, -halfLineHeight);
|
74
|
+
canvasContext.fillRect(lineWidth * 4, halfLineHeight, lineWidth, 0 - getMaxSmallIndicatorHeight(halfValue));
|
75
|
+
}, 200);
|
76
|
+
};
|
@@ -0,0 +1,32 @@
|
|
1
|
+
export default class Collector {
|
2
|
+
constructor(cfg: any, refProbeId: any);
|
3
|
+
_callbacks: {
|
4
|
+
onreport: null;
|
5
|
+
onticket: null;
|
6
|
+
};
|
7
|
+
_id: string;
|
8
|
+
_moduleName: string;
|
9
|
+
_probeId: any;
|
10
|
+
_config: any;
|
11
|
+
_exporter: Exporter;
|
12
|
+
_state: string;
|
13
|
+
analyze(stats: any, previousReport: any, beforeLastReport: any, referenceReport: any): any;
|
14
|
+
takeReferenceStats(): Promise<any>;
|
15
|
+
collectStats(): Promise<any>;
|
16
|
+
start(): Promise<void>;
|
17
|
+
set state(arg: string);
|
18
|
+
get state(): string;
|
19
|
+
_startedTime: Date | undefined;
|
20
|
+
mute(): Promise<void>;
|
21
|
+
unmute(): Promise<void>;
|
22
|
+
stop(forced: any): Promise<void>;
|
23
|
+
_stoppedTime: Date | undefined;
|
24
|
+
registerCallback(name: any, callback: any, context: any): void;
|
25
|
+
unregisterCallback(name: any): void;
|
26
|
+
fireOnReport(report: any): void;
|
27
|
+
fireOnTicket(ticket: any): void;
|
28
|
+
updateConfig(config: any): void;
|
29
|
+
addCustomEvent(at: any, category: any, name: any, description: any): void;
|
30
|
+
registerToPCEvents(): Promise<void>;
|
31
|
+
}
|
32
|
+
import Exporter from "./exporter";
|
@@ -0,0 +1,282 @@
|
|
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 exporter_1 = __importDefault(require("./exporter"));
|
16
|
+
const extractor_1 = require("./extractor");
|
17
|
+
const score_1 = require("./utils/score");
|
18
|
+
const models_1 = require("./utils/models");
|
19
|
+
const helper_1 = require("./utils/helper");
|
20
|
+
const log_1 = require("./utils/log");
|
21
|
+
class Collector {
|
22
|
+
constructor(cfg, refProbeId) {
|
23
|
+
this._callbacks = {
|
24
|
+
onreport: null,
|
25
|
+
onticket: null,
|
26
|
+
};
|
27
|
+
this._id = (0, helper_1.createCollectorId)();
|
28
|
+
this._moduleName = this._id;
|
29
|
+
this._probeId = refProbeId;
|
30
|
+
this._config = cfg;
|
31
|
+
this._exporter = new exporter_1.default(cfg);
|
32
|
+
this._state = models_1.COLLECTOR_STATE.IDLE;
|
33
|
+
this.registerToPCEvents();
|
34
|
+
(0, log_1.info)(this._moduleName, `new collector created for probe ${this._probeId}`);
|
35
|
+
}
|
36
|
+
analyze(stats, previousReport, beforeLastReport, referenceReport) {
|
37
|
+
const getDefaultSSRCMetric = (kind, reportType) => {
|
38
|
+
if (kind === models_1.VALUE.AUDIO) {
|
39
|
+
if (reportType === models_1.TYPE.INBOUND_RTP) {
|
40
|
+
return Object.assign({}, models_1.defaultAudioMetricIn);
|
41
|
+
}
|
42
|
+
return Object.assign({}, models_1.defaultAudioMetricOut);
|
43
|
+
}
|
44
|
+
if (reportType === models_1.TYPE.INBOUND_RTP) {
|
45
|
+
return Object.assign({}, models_1.defaultVideoMetricIn);
|
46
|
+
}
|
47
|
+
return Object.assign({}, models_1.defaultVideoMetricOut);
|
48
|
+
};
|
49
|
+
const report = (0, models_1.getDefaultMetric)(previousReport);
|
50
|
+
report.pname = this._config.pname;
|
51
|
+
report.call_id = this._config.cid;
|
52
|
+
report.user_id = this._config.uid;
|
53
|
+
report.count = previousReport ? previousReport.count + 1 : 1;
|
54
|
+
let timestamp = null;
|
55
|
+
stats.forEach((stat) => {
|
56
|
+
if (!timestamp && stat.timestamp) {
|
57
|
+
timestamp = stat.timestamp;
|
58
|
+
}
|
59
|
+
const values = (0, extractor_1.extract)(stat, report, report.pname, referenceReport);
|
60
|
+
values.forEach((data) => {
|
61
|
+
if (data.value && data.type) {
|
62
|
+
if (data.ssrc) {
|
63
|
+
let ssrcReport = report[data.type][data.ssrc];
|
64
|
+
if (!ssrcReport) {
|
65
|
+
ssrcReport = getDefaultSSRCMetric(data.type, stat.type);
|
66
|
+
ssrcReport.ssrc = data.ssrc;
|
67
|
+
report[data.type][data.ssrc] = (ssrcReport);
|
68
|
+
}
|
69
|
+
Object.keys(data.value).forEach((key) => {
|
70
|
+
ssrcReport[key] = data.value[key];
|
71
|
+
});
|
72
|
+
}
|
73
|
+
else {
|
74
|
+
Object.keys(data.value).forEach((key) => {
|
75
|
+
report[data.type][key] = data.value[key];
|
76
|
+
});
|
77
|
+
}
|
78
|
+
}
|
79
|
+
});
|
80
|
+
});
|
81
|
+
report.timestamp = timestamp;
|
82
|
+
Object.keys(report[models_1.VALUE.AUDIO]).forEach((key) => {
|
83
|
+
const ssrcReport = report[models_1.VALUE.AUDIO][key];
|
84
|
+
if (ssrcReport.direction === models_1.DIRECTION.INBOUND) {
|
85
|
+
ssrcReport.mos_emodel_in = (0, score_1.computeEModelMOS)(report, models_1.VALUE.AUDIO, previousReport, beforeLastReport, ssrcReport.ssrc);
|
86
|
+
ssrcReport.mos_in = (0, score_1.computeMOS)(report, models_1.VALUE.AUDIO, previousReport, beforeLastReport, ssrcReport.ssrc);
|
87
|
+
}
|
88
|
+
else {
|
89
|
+
ssrcReport.mos_emodel_out = (0, score_1.computeEModelMOSForOutgoing)(report, models_1.VALUE.AUDIO, previousReport, beforeLastReport, ssrcReport.ssrc);
|
90
|
+
ssrcReport.mos_out = (0, score_1.computeMOSForOutgoing)(report, models_1.VALUE.AUDIO, previousReport, beforeLastReport, ssrcReport.ssrc);
|
91
|
+
}
|
92
|
+
});
|
93
|
+
return report;
|
94
|
+
}
|
95
|
+
takeReferenceStats() {
|
96
|
+
return __awaiter(this, void 0, void 0, function* () {
|
97
|
+
return new Promise((resolve, reject) => {
|
98
|
+
const preWaitTime = Date.now();
|
99
|
+
setTimeout(() => __awaiter(this, void 0, void 0, function* () {
|
100
|
+
try {
|
101
|
+
const waitTime = Date.now() - preWaitTime;
|
102
|
+
const preTime = Date.now();
|
103
|
+
const reports = yield this._config.pc.getStats();
|
104
|
+
const referenceReport = this.analyze(reports, null, null, null);
|
105
|
+
const postTime = Date.now();
|
106
|
+
referenceReport.experimental.time_to_measure_ms = postTime - preTime;
|
107
|
+
referenceReport.experimental.time_to_wait_ms = waitTime;
|
108
|
+
this._exporter.saveReferenceReport(referenceReport);
|
109
|
+
(0, log_1.debug)(this._moduleName, `got reference report for probe ${this._probeId}`);
|
110
|
+
resolve();
|
111
|
+
}
|
112
|
+
catch (err) {
|
113
|
+
reject(err);
|
114
|
+
}
|
115
|
+
}), this._config.startAfter);
|
116
|
+
});
|
117
|
+
});
|
118
|
+
}
|
119
|
+
collectStats() {
|
120
|
+
return __awaiter(this, void 0, void 0, function* () {
|
121
|
+
try {
|
122
|
+
if (this._state !== models_1.COLLECTOR_STATE.RUNNING || !this._config.pc) {
|
123
|
+
(0, log_1.debug)(this._moduleName, `report discarded (too late) for probe ${this._probeId}`);
|
124
|
+
return null;
|
125
|
+
}
|
126
|
+
// Take into account last report in case no report have been generated (eg: candidate-pair)
|
127
|
+
const preTime = Date.now();
|
128
|
+
const reports = yield this._config.pc.getStats();
|
129
|
+
const report = this.analyze(reports, this._exporter.getLastReport(), this._exporter.getBeforeLastReport(), this._exporter.getReferenceReport());
|
130
|
+
const postTime = Date.now();
|
131
|
+
report.experimental.time_to_measure_ms = postTime - preTime;
|
132
|
+
this._exporter.addReport(report);
|
133
|
+
(0, log_1.debug)(this._moduleName, `got report for probe ${this._probeId}#${this._exporter.getReportsNumber() + 1}`);
|
134
|
+
this.fireOnReport(report);
|
135
|
+
return report;
|
136
|
+
}
|
137
|
+
catch (err) {
|
138
|
+
(0, log_1.error)(this._moduleName, `got error ${err}`);
|
139
|
+
return null;
|
140
|
+
}
|
141
|
+
});
|
142
|
+
}
|
143
|
+
start() {
|
144
|
+
return __awaiter(this, void 0, void 0, function* () {
|
145
|
+
(0, log_1.debug)(this._moduleName, "starting");
|
146
|
+
this.state = models_1.COLLECTOR_STATE.RUNNING;
|
147
|
+
this._startedTime = this._exporter.start();
|
148
|
+
(0, log_1.debug)(this._moduleName, "started");
|
149
|
+
});
|
150
|
+
}
|
151
|
+
mute() {
|
152
|
+
return __awaiter(this, void 0, void 0, function* () {
|
153
|
+
this.state = models_1.COLLECTOR_STATE.MUTED;
|
154
|
+
(0, log_1.debug)(this._moduleName, "muted");
|
155
|
+
});
|
156
|
+
}
|
157
|
+
unmute() {
|
158
|
+
return __awaiter(this, void 0, void 0, function* () {
|
159
|
+
this.state = models_1.COLLECTOR_STATE.RUNNING;
|
160
|
+
(0, log_1.debug)(this._moduleName, "unmuted");
|
161
|
+
});
|
162
|
+
}
|
163
|
+
stop(forced) {
|
164
|
+
return __awaiter(this, void 0, void 0, function* () {
|
165
|
+
(0, log_1.debug)(this._moduleName, `stopping${forced ? " by watchdog" : ""}...`);
|
166
|
+
this._stoppedTime = this._exporter.stop();
|
167
|
+
this.state = models_1.COLLECTOR_STATE.IDLE;
|
168
|
+
if (this._config.ticket) {
|
169
|
+
const { ticket } = this._exporter;
|
170
|
+
this.fireOnTicket(ticket);
|
171
|
+
}
|
172
|
+
this._exporter.reset();
|
173
|
+
(0, log_1.debug)(this._moduleName, "stopped");
|
174
|
+
});
|
175
|
+
}
|
176
|
+
registerCallback(name, callback, context) {
|
177
|
+
if (name in this._callbacks) {
|
178
|
+
this._callbacks[name] = {
|
179
|
+
callback,
|
180
|
+
context,
|
181
|
+
};
|
182
|
+
(0, log_1.debug)(this._moduleName, `registered callback '${name}'`);
|
183
|
+
}
|
184
|
+
else {
|
185
|
+
(0, log_1.error)(this._moduleName, `can't register callback for '${name}' - not found`);
|
186
|
+
}
|
187
|
+
}
|
188
|
+
unregisterCallback(name) {
|
189
|
+
if (name in this._callbacks) {
|
190
|
+
this._callbacks[name] = null;
|
191
|
+
delete this._callbacks[name];
|
192
|
+
(0, log_1.debug)(this._moduleName, `unregistered callback '${name}'`);
|
193
|
+
}
|
194
|
+
else {
|
195
|
+
(0, log_1.error)(this._moduleName, `can't unregister callback for '${name}' - not found`);
|
196
|
+
}
|
197
|
+
}
|
198
|
+
fireOnReport(report) {
|
199
|
+
if (this._callbacks.onreport) {
|
200
|
+
(0, helper_1.call)(this._callbacks.onreport.callback, this._callbacks.onreport.context, report);
|
201
|
+
}
|
202
|
+
}
|
203
|
+
fireOnTicket(ticket) {
|
204
|
+
if (this._callbacks.onticket) {
|
205
|
+
(0, helper_1.call)(this._callbacks.onticket.callback, this._callbacks.onticket.context, ticket);
|
206
|
+
}
|
207
|
+
}
|
208
|
+
updateConfig(config) {
|
209
|
+
this._config = config;
|
210
|
+
this._exporter.updateConfig(config);
|
211
|
+
}
|
212
|
+
get state() {
|
213
|
+
return this._state;
|
214
|
+
}
|
215
|
+
set state(newState) {
|
216
|
+
this._state = newState;
|
217
|
+
(0, log_1.debug)(this._moduleName, `state changed to ${newState}`);
|
218
|
+
}
|
219
|
+
addCustomEvent(at, category, name, description) {
|
220
|
+
this._exporter.addCustomEvent({
|
221
|
+
at: typeof at === "object" ? at.toJSON() : at,
|
222
|
+
category,
|
223
|
+
name,
|
224
|
+
description,
|
225
|
+
});
|
226
|
+
}
|
227
|
+
registerToPCEvents() {
|
228
|
+
return __awaiter(this, void 0, void 0, function* () {
|
229
|
+
const { pc } = this._config;
|
230
|
+
navigator.mediaDevices.ondevicechange = () => __awaiter(this, void 0, void 0, function* () {
|
231
|
+
try {
|
232
|
+
const devices = yield navigator.mediaDevices.enumerateDevices();
|
233
|
+
this.addCustomEvent(new Date().toJSON(), "device", `${devices.length} devices found`, "Media Devices state");
|
234
|
+
// eslint-disable-next-line no-empty
|
235
|
+
}
|
236
|
+
catch (err) {
|
237
|
+
(0, log_1.error)(this._moduleName, "can't get devices");
|
238
|
+
}
|
239
|
+
});
|
240
|
+
if (pc) {
|
241
|
+
pc.oniceconnectionstatechange = () => {
|
242
|
+
const value = pc.iceConnectionState;
|
243
|
+
if (value === models_1.ICE_CONNECTION_STATE.CONNECTED ||
|
244
|
+
value === models_1.ICE_CONNECTION_STATE.COMPLETED) {
|
245
|
+
this.addCustomEvent(new Date().toJSON(), "call", value, "ICE connection state");
|
246
|
+
}
|
247
|
+
else if (value === models_1.ICE_CONNECTION_STATE.DISCONNECTED ||
|
248
|
+
value === models_1.ICE_CONNECTION_STATE.FAILED) {
|
249
|
+
this.addCustomEvent(new Date().toJSON(), "call", value, "ICE connection state");
|
250
|
+
}
|
251
|
+
else if (value === models_1.ICE_CONNECTION_STATE.CLOSED) {
|
252
|
+
this.addCustomEvent(new Date().toJSON(), "call", "ended", "ICE connection state");
|
253
|
+
}
|
254
|
+
};
|
255
|
+
pc.onicegatheringstatechange = () => {
|
256
|
+
const value = pc.iceGatheringState;
|
257
|
+
this.addCustomEvent(new Date().toJSON(), "call", value, "ICE gathering state");
|
258
|
+
};
|
259
|
+
pc.ontrack = (e) => {
|
260
|
+
this.addCustomEvent(new Date().toJSON(), "call", `${e.track.kind}track`, "MediaStreamTrack received");
|
261
|
+
};
|
262
|
+
pc.onnegotiationneeded = () => {
|
263
|
+
this.addCustomEvent(new Date().toJSON(), "call", "negotiation", "Media changed");
|
264
|
+
};
|
265
|
+
const receivers = pc.getReceivers();
|
266
|
+
if (receivers && receivers.length > 0) {
|
267
|
+
const receiver = receivers[0];
|
268
|
+
const { transport } = receiver;
|
269
|
+
if (transport) {
|
270
|
+
const { iceTransport } = transport;
|
271
|
+
if (iceTransport) {
|
272
|
+
iceTransport.onselectedcandidatepairchange = () => {
|
273
|
+
this.addCustomEvent(new Date().toJSON(), "call", "transport", "Candidates Pair changed");
|
274
|
+
};
|
275
|
+
}
|
276
|
+
}
|
277
|
+
}
|
278
|
+
}
|
279
|
+
});
|
280
|
+
}
|
281
|
+
}
|
282
|
+
exports.default = Collector;
|