@telegenta/webclient 3.2.2 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/demos/phone_client.html +3 -0
- package/dist/telegenta.js +72 -20
- package/dist/telegenta.min.js +1 -1
- package/docs/Phone.html +2 -2
- package/docs/Session.html +2 -2
- package/docs/global.html +3 -3
- package/docs/phone-client.js.html +51 -7
- package/package.json +1 -1
package/demos/phone_client.html
CHANGED
package/dist/telegenta.js
CHANGED
|
@@ -219,15 +219,15 @@ function createUUID() {
|
|
|
219
219
|
d += performance.now(); //use high-precision timer if available
|
|
220
220
|
}
|
|
221
221
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
222
|
-
let r = (d + Math.random() * 16) % 16 | 0;
|
|
222
|
+
let r = ((d + Math.random() * 16) % 16) | 0;
|
|
223
223
|
d = Math.floor(d / 16);
|
|
224
|
-
return (c === 'x' ? r : (r & 0x3 | 0x8)
|
|
224
|
+
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
|
|
225
225
|
});
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
// Compare properties of two objects. Comparison check if all properties in obj1 exists and has same values in obj2
|
|
229
229
|
function compareObjects(obj1, obj2) {
|
|
230
|
-
if (
|
|
230
|
+
if (!obj1 || !obj2) {
|
|
231
231
|
return false;
|
|
232
232
|
}
|
|
233
233
|
|
|
@@ -241,7 +241,7 @@ function compareObjects(obj1, obj2) {
|
|
|
241
241
|
return false;
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
-
return
|
|
244
|
+
return obj1[k] === obj2[k];
|
|
245
245
|
});
|
|
246
246
|
}
|
|
247
247
|
|
|
@@ -257,13 +257,13 @@ function createDurationString(milliSeconds) {
|
|
|
257
257
|
}
|
|
258
258
|
// Hour
|
|
259
259
|
if (seconds / 3600 >= 1) {
|
|
260
|
-
s = s + Math.floor(seconds / 3600) % 24 + 'h ';
|
|
260
|
+
s = s + (Math.floor(seconds / 3600) % 24) + 'h ';
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
// Seconds
|
|
264
264
|
if (seconds >= 0) {
|
|
265
|
-
s = s + (
|
|
266
|
-
s = s + (
|
|
265
|
+
s = s + ('00' + (Math.floor(seconds / 60) % 60)).slice(-2) + ':';
|
|
266
|
+
s = s + ('00' + (seconds % 60)).slice(-2);
|
|
267
267
|
} else {
|
|
268
268
|
s = '00:00';
|
|
269
269
|
}
|
|
@@ -273,23 +273,23 @@ function createDurationString(milliSeconds) {
|
|
|
273
273
|
|
|
274
274
|
// Browser checks
|
|
275
275
|
function browserIsOpera() {
|
|
276
|
-
return (
|
|
276
|
+
return (navigator.userAgent.indexOf('Opera') || navigator.userAgent.indexOf('OPR')) !== -1;
|
|
277
277
|
}
|
|
278
278
|
|
|
279
279
|
function browserIsChrome() {
|
|
280
|
-
return
|
|
280
|
+
return navigator.userAgent.indexOf('Chrome') !== -1;
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
function browserIsFirefox() {
|
|
284
|
-
return
|
|
284
|
+
return navigator.userAgent.indexOf('Firefox') !== -1;
|
|
285
285
|
}
|
|
286
286
|
|
|
287
287
|
function browserIsSafari() {
|
|
288
|
-
return
|
|
288
|
+
return navigator.userAgent.indexOf('Safari') !== -1;
|
|
289
289
|
}
|
|
290
290
|
|
|
291
291
|
function browserIsEdge() {
|
|
292
|
-
return
|
|
292
|
+
return navigator.userAgent.indexOf('Edge') !== -1;
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
// SIP header names are RFC 3261 tokens
|
|
@@ -329,6 +329,14 @@ function buildCustomHeaders(setHeader) {
|
|
|
329
329
|
});
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
+
// Rewrite Opus stereo=1 to stereo=0 on every a=fmtp line.
|
|
333
|
+
// Firefox always offers stereo=1 and Media Switches then opens a 2-channel leg, on which
|
|
334
|
+
// the recording carries the same mix on both channels.
|
|
335
|
+
// The result is recordings contain identical audio on both channels, affecting transcription diarization.
|
|
336
|
+
function withoutOpusStereo(sdp) {
|
|
337
|
+
return sdp.replace(/^a=fmtp:.*$/gm, (line) => line.replace(/stereo=1/g, 'stereo=0'));
|
|
338
|
+
}
|
|
339
|
+
|
|
332
340
|
// Request timeout after 10 sec
|
|
333
341
|
const REQUEST_TIMEOUT = 10000;
|
|
334
342
|
|
|
@@ -1559,6 +1567,7 @@ const options = {
|
|
|
1559
1567
|
autoRemoveSessionTime: 3000,
|
|
1560
1568
|
playRingOnIncoming: true,
|
|
1561
1569
|
playCallInitializedTone: false,
|
|
1570
|
+
playCallEndedTone: false,
|
|
1562
1571
|
};
|
|
1563
1572
|
|
|
1564
1573
|
// DTMF Frequency table
|
|
@@ -1595,6 +1604,11 @@ const ST_CALLINIT = [
|
|
|
1595
1604
|
{ d: 200, f1: 500, f2: 0, g: 0 },
|
|
1596
1605
|
{ d: 200, f1: 700, f2: 0, g: 5000 },
|
|
1597
1606
|
];
|
|
1607
|
+
const ST_CALLENDED = [
|
|
1608
|
+
{ d: 150, f1: 600, f2: 0, g: 50 },
|
|
1609
|
+
{ d: 150, f1: 500, f2: 0, g: 50 },
|
|
1610
|
+
{ d: 150, f1: 400, f2: 0, g: 0 },
|
|
1611
|
+
];
|
|
1598
1612
|
const ST_RINGBACK = [{ d: 1000, f1: 425, f2: 0, g: 4000 }];
|
|
1599
1613
|
const ST_INCOMING = [
|
|
1600
1614
|
{ d: 150, f1: 400, f2: 0, g: 0 },
|
|
@@ -1753,10 +1767,7 @@ class Phone extends EventEmitter {
|
|
|
1753
1767
|
return false;
|
|
1754
1768
|
}
|
|
1755
1769
|
const sinkId = deviceId || '';
|
|
1756
|
-
await Promise.all([
|
|
1757
|
-
this._remoteAudioElement.setSinkId(sinkId),
|
|
1758
|
-
this._toneAudioElement.setSinkId(sinkId),
|
|
1759
|
-
]);
|
|
1770
|
+
await Promise.all([this._remoteAudioElement.setSinkId(sinkId), this._toneAudioElement.setSinkId(sinkId)]);
|
|
1760
1771
|
return true;
|
|
1761
1772
|
}
|
|
1762
1773
|
|
|
@@ -1778,6 +1789,7 @@ class Phone extends EventEmitter {
|
|
|
1778
1789
|
* @param {number} [phoneOptions.autoRemoveSessionTime=3000] Automatically remove session after interval specified
|
|
1779
1790
|
* @param {number} [phoneOptions.playRingOnIncoming=true] Play ringing tones on incoming calls
|
|
1780
1791
|
* @param {number} [phoneOptions.playCallInitializedTone=false] Play tone when call has been initiated (before progress/ringback tones)
|
|
1792
|
+
* @param {boolean} [phoneOptions.playCallEndedTone=false] Play tone once when an answered call ends without the agent hanging up (remote hangup, server hangup, connection loss, timeouts)
|
|
1781
1793
|
*
|
|
1782
1794
|
* @throws MediaNotPrepared - If {@link prepareMedia} was not called and resolved prior to calling this method
|
|
1783
1795
|
* @throws InvalidOptions - If one or more options are invalid
|
|
@@ -2213,14 +2225,18 @@ class Phone extends EventEmitter {
|
|
|
2213
2225
|
|
|
2214
2226
|
// Set timer to stop tone again
|
|
2215
2227
|
const self = this;
|
|
2216
|
-
|
|
2217
|
-
|
|
2228
|
+
const timer = setTimeout(function () {
|
|
2229
|
+
// A later playTone() has taken over the oscillators; don't silence it or cancel its timer
|
|
2230
|
+
if (self._toneTimer === timer) {
|
|
2231
|
+
self.stopTone();
|
|
2232
|
+
}
|
|
2218
2233
|
|
|
2219
2234
|
// Handle callback now
|
|
2220
2235
|
if (callback) {
|
|
2221
2236
|
callback();
|
|
2222
2237
|
}
|
|
2223
2238
|
}, duration);
|
|
2239
|
+
this._toneTimer = timer;
|
|
2224
2240
|
}
|
|
2225
2241
|
|
|
2226
2242
|
/**
|
|
@@ -2263,6 +2279,18 @@ class Phone extends EventEmitter {
|
|
|
2263
2279
|
}
|
|
2264
2280
|
}
|
|
2265
2281
|
|
|
2282
|
+
// Play a tone sequence once. A repeat tone starting meanwhile (e.g. a new call's ringing) cancels the rest.
|
|
2283
|
+
_playToneSequence(sequence, pos = 0) {
|
|
2284
|
+
const toneDef = sequence[pos];
|
|
2285
|
+
if (!toneDef || this._repeatToneActive) {
|
|
2286
|
+
return;
|
|
2287
|
+
}
|
|
2288
|
+
const self = this;
|
|
2289
|
+
this.playTone(toneDef.d, toneDef.f1, toneDef.f2, function () {
|
|
2290
|
+
setTimeout(self._playToneSequence.bind(self, sequence, pos + 1), toneDef.g);
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2266
2294
|
// End repeating tones
|
|
2267
2295
|
_endRepeatTone() {
|
|
2268
2296
|
if (this._repeatToneActive) {
|
|
@@ -2592,6 +2620,7 @@ class Session extends EventEmitter {
|
|
|
2592
2620
|
this._isListenCall = false;
|
|
2593
2621
|
this._whisperMode = null;
|
|
2594
2622
|
this._manualDisposition = null;
|
|
2623
|
+
this._transferInitiated = false;
|
|
2595
2624
|
|
|
2596
2625
|
// Quality report
|
|
2597
2626
|
this._rtcpStatsTimer = null;
|
|
@@ -2752,6 +2781,13 @@ class Session extends EventEmitter {
|
|
|
2752
2781
|
// Hookup events
|
|
2753
2782
|
const self = this;
|
|
2754
2783
|
|
|
2784
|
+
// make sure to remove stereo=1 from the SDP
|
|
2785
|
+
this._webRTCSession.on('sdp', function (e) {
|
|
2786
|
+
if (e.originator === 'local') {
|
|
2787
|
+
e.sdp = withoutOpusStereo(e.sdp);
|
|
2788
|
+
}
|
|
2789
|
+
});
|
|
2790
|
+
|
|
2755
2791
|
// ICE CANDIDATES
|
|
2756
2792
|
this._webRTCSession.on('icecandidate', function (event) {
|
|
2757
2793
|
const c = event.candidate;
|
|
@@ -2851,10 +2887,17 @@ class Session extends EventEmitter {
|
|
|
2851
2887
|
self._setSessionState('ACTIVE');
|
|
2852
2888
|
});
|
|
2853
2889
|
|
|
2854
|
-
this._webRTCSession.on('ended', function (
|
|
2890
|
+
this._webRTCSession.on('ended', function (data) {
|
|
2855
2891
|
// End any ringsbacks if needed
|
|
2856
2892
|
self._phone._endRepeatTone();
|
|
2857
2893
|
|
|
2894
|
+
// jssip fires 'ended' once a call is answered. Its own errors (connection loss, timeouts, ICE failure)
|
|
2895
|
+
// also end as 'local' but with their own cause, so only a local BYE is the agent hanging up.
|
|
2896
|
+
const agentHangup = data.originator === 'local' && data.cause === JsSIP.C.causes.BYE;
|
|
2897
|
+
if (self._phone.options.playCallEndedTone && !agentHangup && !self._transferInitiated) {
|
|
2898
|
+
self._phone._playToneSequence(ST_CALLENDED);
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2858
2901
|
self._callEndTime = Date.now();
|
|
2859
2902
|
|
|
2860
2903
|
// Clear answer call timeout timer if needed
|
|
@@ -3296,6 +3339,10 @@ class Session extends EventEmitter {
|
|
|
3296
3339
|
transfer(dstSession) {
|
|
3297
3340
|
this.log.info(`Transferring ${this.uuid} => ${dstSession.uuid}`);
|
|
3298
3341
|
|
|
3342
|
+
// The server hangs up both legs once the transfer completes - that's the agent's doing, no call-ended tone
|
|
3343
|
+
this._transferInitiated = true;
|
|
3344
|
+
dstSession._transferInitiated = true;
|
|
3345
|
+
|
|
3299
3346
|
// Create ReferSubscriber event class
|
|
3300
3347
|
const referSubscriber = this._webRTCSession.refer(this._webRTCSession._request.ruri, {
|
|
3301
3348
|
replaces: dstSession._webRTCSession,
|
|
@@ -3310,10 +3357,15 @@ class Session extends EventEmitter {
|
|
|
3310
3357
|
|
|
3311
3358
|
referSubscriber.on('requestFailed', function (cause) {
|
|
3312
3359
|
self.log.error(`Transfer rejected: ${cause}`);
|
|
3360
|
+
// Legs stay up, so a later hangup by the other party should get the cue
|
|
3361
|
+
self._transferInitiated = false;
|
|
3362
|
+
dstSession._transferInitiated = false;
|
|
3313
3363
|
});
|
|
3314
3364
|
|
|
3315
3365
|
referSubscriber.on('failed', function (cause) {
|
|
3316
3366
|
self.log.error(`Transfer failed: ${cause}`);
|
|
3367
|
+
self._transferInitiated = false;
|
|
3368
|
+
dstSession._transferInitiated = false;
|
|
3317
3369
|
});
|
|
3318
3370
|
}
|
|
3319
3371
|
|
|
@@ -3881,4 +3933,4 @@ class Session extends EventEmitter {
|
|
|
3881
3933
|
}
|
|
3882
3934
|
}
|
|
3883
3935
|
|
|
3884
|
-
export { EventClient, EventServiceNotConnected, InvalidMediaDevice, InvalidOptions, InvalidParameter, LOGLVL, LogHandler, MediaNotPrepared, NoActiveSession, NotAllowed, NotReady, Phone, Request, Requestor, Session, UnableToConnect, browserIsChrome, browserIsEdge, browserIsFirefox, browserIsOpera, browserIsSafari, buildCustomHeaders, compareObjects, createDurationString, createUUID };
|
|
3936
|
+
export { EventClient, EventServiceNotConnected, InvalidMediaDevice, InvalidOptions, InvalidParameter, LOGLVL, LogHandler, MediaNotPrepared, NoActiveSession, NotAllowed, NotReady, Phone, Request, Requestor, Session, UnableToConnect, browserIsChrome, browserIsEdge, browserIsFirefox, browserIsOpera, browserIsSafari, buildCustomHeaders, compareObjects, createDurationString, createUUID, withoutOpusStereo };
|