@babblevoice/babble-drachtio-callmanager 1.4.3 → 1.5.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/lib/call.js +107 -54
- package/package.json +1 -1
- package/test/interface/early.js +102 -0
- package/test/mock/srf.js +5 -0
package/lib/call.js
CHANGED
|
@@ -90,6 +90,7 @@ class call {
|
|
|
90
90
|
/**
|
|
91
91
|
@typedef { Object } callstate
|
|
92
92
|
@property { boolean } trying
|
|
93
|
+
@property { boolean } early
|
|
93
94
|
@property { boolean } ringing
|
|
94
95
|
@property { boolean } established
|
|
95
96
|
@property { boolean } canceled
|
|
@@ -103,6 +104,7 @@ class call {
|
|
|
103
104
|
/** @member { callstate } */
|
|
104
105
|
this.state = {
|
|
105
106
|
"trying": false,
|
|
107
|
+
"early": false,
|
|
106
108
|
"ringing": false,
|
|
107
109
|
"established": false,
|
|
108
110
|
"canceled": false,
|
|
@@ -694,6 +696,12 @@ class call {
|
|
|
694
696
|
Emitted when a call is ringing
|
|
695
697
|
@event call.ringing
|
|
696
698
|
@type {call}
|
|
699
|
+
*/
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
Emitted when a call is receives early
|
|
703
|
+
@event call.early
|
|
704
|
+
@type {call}
|
|
697
705
|
*/
|
|
698
706
|
|
|
699
707
|
/**
|
|
@@ -812,6 +820,33 @@ class call {
|
|
|
812
820
|
callmanager.options.em.emit( "call.ringing", this )
|
|
813
821
|
}
|
|
814
822
|
|
|
823
|
+
/**
|
|
824
|
+
Called from newuac when we receive a 183
|
|
825
|
+
@private
|
|
826
|
+
*/
|
|
827
|
+
async _onearly() {
|
|
828
|
+
|
|
829
|
+
if( this.state.established ) return
|
|
830
|
+
if( !this._res || !this._res.msg || !this._res.msg.body ) return
|
|
831
|
+
|
|
832
|
+
/* we have this._res */
|
|
833
|
+
this.sdp.remote = sdpgen.create( this._res.msg.body )
|
|
834
|
+
await this.answer( { "early": true } )
|
|
835
|
+
|
|
836
|
+
if( !this.parent || this.parent.state.established ) return
|
|
837
|
+
|
|
838
|
+
await this.parent.answer( { "early": true } )
|
|
839
|
+
await this.channels.audio.mix( this.parent.channels.audio )
|
|
840
|
+
|
|
841
|
+
this.parent._res.send( 183, {
|
|
842
|
+
headers: {
|
|
843
|
+
"User-Agent": "project",
|
|
844
|
+
"Supported": "replaces"
|
|
845
|
+
},
|
|
846
|
+
"body": this.sdp.local.toString()
|
|
847
|
+
} )
|
|
848
|
+
}
|
|
849
|
+
|
|
815
850
|
/**
|
|
816
851
|
Called from newuac when we are answered and we have a dialog,
|
|
817
852
|
this = child call (the new call - the bleg)
|
|
@@ -1303,80 +1338,96 @@ class call {
|
|
|
1303
1338
|
}
|
|
1304
1339
|
|
|
1305
1340
|
/**
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1341
|
+
* Answer this (inbound) call and store a channel which can be used. This framework will catch and cleanup this call if this is rejected.
|
|
1342
|
+
* @param { object } options
|
|
1343
|
+
* @param { boolean } options.early - don't answer the channel (establish) but establish early media (respond to 183).
|
|
1344
|
+
*
|
|
1345
|
+
* @return {Promise} Returns a promise which resolves if the call is answered, otherwise rejects the promise.
|
|
1309
1346
|
*/
|
|
1310
1347
|
async answer( options = {} ) {
|
|
1311
1348
|
|
|
1312
1349
|
if( this.canceled || this.established ) return
|
|
1313
1350
|
|
|
1314
1351
|
options = { ...callmanager.options, ...this.options, ...options }
|
|
1315
|
-
this.sdp.remote = sdpgen.create( this._req.msg.body )
|
|
1316
|
-
|
|
1317
|
-
/* options.preferedcodecs may have been narrowed down so we still check callmanager as well */
|
|
1318
|
-
this.selectedcodec = this.sdp.remote.intersection( options.preferedcodecs, true )
|
|
1319
|
-
if( false === this.selectedcodec ) {
|
|
1320
|
-
this.selectedcodec = this.sdp.remote.intersection( callmanager.options.preferedcodecs, true )
|
|
1321
|
-
}
|
|
1322
1352
|
|
|
1323
|
-
let
|
|
1324
|
-
if(
|
|
1353
|
+
let channeldef
|
|
1354
|
+
if( this._req.msg && this._req.msg.body ) {
|
|
1355
|
+
this.sdp.remote = sdpgen.create( this._req.msg.body )
|
|
1356
|
+
/* options.preferedcodecs may have been narrowed down so we still check callmanager as well */
|
|
1357
|
+
this.selectedcodec = this.sdp.remote.intersection( options.preferedcodecs, true )
|
|
1358
|
+
if( false === this.selectedcodec ) {
|
|
1359
|
+
this.selectedcodec = this.sdp.remote.intersection( callmanager.options.preferedcodecs, true )
|
|
1360
|
+
}
|
|
1361
|
+
let remoteaudio = this.sdp.remote.getaudio()
|
|
1362
|
+
if( !remoteaudio ) return
|
|
1325
1363
|
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1364
|
+
this.sdp.remote.select( this.selectedcodec )
|
|
1365
|
+
|
|
1366
|
+
await call._parsesdpcandidates( remoteaudio, this.sdp.remote.sdp )
|
|
1329
1367
|
|
|
1330
|
-
|
|
1368
|
+
channeldef = call._createchannelremotedef( remoteaudio.address, remoteaudio.port, remoteaudio.audio.payloads[ 0 ] )
|
|
1331
1369
|
|
|
1332
|
-
|
|
1370
|
+
if( this._iswebrtc ) {
|
|
1371
|
+
channeldef.remote.dtls = {
|
|
1372
|
+
"fingerprint": this.sdp.remote.sdp.media[ 0 ].fingerprint,
|
|
1373
|
+
"mode": this.sdp.remote.sdp.media[ 0 ].setup==="passive"?"active":"passive" /* prefer passive for us */
|
|
1374
|
+
}
|
|
1333
1375
|
|
|
1334
|
-
|
|
1335
|
-
channeldef.remote.dtls = {
|
|
1336
|
-
"fingerprint": this.sdp.remote.sdp.media[ 0 ].fingerprint,
|
|
1337
|
-
"mode": this.sdp.remote.sdp.media[ 0 ].setup==="passive"?"active":"passive" /* prefer passive for us */
|
|
1376
|
+
channeldef.remote.icepwd = this.sdp.remote.sdp.media[ 0 ].icePwd
|
|
1338
1377
|
}
|
|
1339
|
-
|
|
1340
|
-
channeldef.remote.icepwd = this.sdp.remote.sdp.media[ 0 ].icePwd
|
|
1341
1378
|
}
|
|
1342
1379
|
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1380
|
+
/*
|
|
1381
|
+
We might have already opened our audio when we received 183 (early).
|
|
1382
|
+
*/
|
|
1383
|
+
if( this.channels.audio ) {
|
|
1384
|
+
this.channels.audio.remote( channeldef.remote )
|
|
1385
|
+
} else {
|
|
1386
|
+
let ch = await projectrtp.openchannel( channeldef, this._handlechannelevents.bind( this ) )
|
|
1387
|
+
this.channels.audio = ch
|
|
1388
|
+
this.sdp.local = sdpgen.create()
|
|
1389
|
+
.addcodecs( this.selectedcodec )
|
|
1390
|
+
.setconnectionaddress( ch.local.address )
|
|
1391
|
+
.setaudioport( ch.local.port )
|
|
1392
|
+
|
|
1393
|
+
if( callmanager.options.rfc2833 ) {
|
|
1394
|
+
this.sdp.local.addcodecs( "2833" )
|
|
1395
|
+
}
|
|
1349
1396
|
|
|
1350
|
-
|
|
1397
|
+
if( this._iswebrtc ) {
|
|
1398
|
+
this.sdp.local.addssrc( ch.local.ssrc )
|
|
1399
|
+
.secure( ch.local.dtls.fingerprint, channeldef.remote.dtls.mode )
|
|
1400
|
+
.addicecandidates( ch.local.address, ch.local.port, ch.local.icepwd )
|
|
1401
|
+
.rtcpmux()
|
|
1402
|
+
}
|
|
1351
1403
|
|
|
1352
|
-
|
|
1353
|
-
this.sdp.local.addcodecs( "2833" )
|
|
1404
|
+
if( this.canceled ) return
|
|
1354
1405
|
}
|
|
1355
1406
|
|
|
1356
|
-
if(
|
|
1357
|
-
this.
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
.rtcpmux()
|
|
1361
|
-
}
|
|
1407
|
+
if( options.early ) {
|
|
1408
|
+
this.state.early = true
|
|
1409
|
+
this._em.emit( "call.early", this )
|
|
1410
|
+
callmanager.options.em.emit( "call.early", this )
|
|
1362
1411
|
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1412
|
+
} else {
|
|
1413
|
+
let dialog = await callmanager.options.srf.createUAS( this._req, this._res, {
|
|
1414
|
+
localSdp: this.sdp.local.toString(),
|
|
1415
|
+
headers: {
|
|
1416
|
+
"User-Agent": "project",
|
|
1417
|
+
"Supported": "replaces"
|
|
1418
|
+
}
|
|
1419
|
+
} )
|
|
1370
1420
|
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1421
|
+
this.established = true
|
|
1422
|
+
this._dialog = dialog
|
|
1423
|
+
this.sip.tags.local = dialog.sip.localTag
|
|
1424
|
+
callstore.set( this )
|
|
1375
1425
|
|
|
1376
|
-
|
|
1426
|
+
this._addevents( this._dialog )
|
|
1427
|
+
this._em.emit( "call.answered", this )
|
|
1428
|
+
callmanager.options.em.emit( "call.answered", this )
|
|
1377
1429
|
|
|
1378
|
-
|
|
1379
|
-
callmanager.options.em.emit( "call.answered", this )
|
|
1430
|
+
}
|
|
1380
1431
|
}
|
|
1381
1432
|
|
|
1382
1433
|
/**
|
|
@@ -1940,7 +1991,7 @@ class call {
|
|
|
1940
1991
|
let target = a_1.sdp.remote.getaudio()
|
|
1941
1992
|
if( target ) {
|
|
1942
1993
|
/* we should always get in here */
|
|
1943
|
-
a_1.channels.audio.remote( call._createchannelremotedef( target.address, target.port, a_1.selectedcodec ) )
|
|
1994
|
+
a_1.channels.audio.remote( call._createchannelremotedef( target.address, target.port, a_1.selectedcodec ).remote )
|
|
1944
1995
|
|
|
1945
1996
|
/* there might be situations where mix is not the correct thing - perhaps a pop push application? */
|
|
1946
1997
|
a_1.channels.audio.mix( c_1.channels.audio )
|
|
@@ -2472,10 +2523,12 @@ class call {
|
|
|
2472
2523
|
if( callbacks && callbacks.early ) callbacks.early( newcall )
|
|
2473
2524
|
callmanager.options.em.emit( "call.new", newcall )
|
|
2474
2525
|
},
|
|
2475
|
-
cbProvisional: ( res ) => {
|
|
2526
|
+
cbProvisional: async ( res ) => {
|
|
2476
2527
|
newcall._res = res
|
|
2477
2528
|
if( 180 === res.status ) {
|
|
2478
2529
|
newcall._onring()
|
|
2530
|
+
} else if( 183 === res.status ) {
|
|
2531
|
+
await newcall._onearly()
|
|
2479
2532
|
}
|
|
2480
2533
|
|
|
2481
2534
|
if( newcall.canceled ) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
const { v4: uuidv4 } = require( "uuid" )
|
|
4
|
+
const expect = require( "chai" ).expect
|
|
5
|
+
const callmanager = require( "../../index.js" )
|
|
6
|
+
const call = require( "../../lib/call.js" )
|
|
7
|
+
const srf = require( "../mock/srf.js" )
|
|
8
|
+
|
|
9
|
+
const projectrtpmessage = require( "@babblevoice/projectrtp/lib/message.js" )
|
|
10
|
+
const net = require( "net" )
|
|
11
|
+
|
|
12
|
+
const clearcallmanager = require( "../../lib/callmanager.js" )._clear
|
|
13
|
+
|
|
14
|
+
describe( "call early", function() {
|
|
15
|
+
|
|
16
|
+
afterEach( function() {
|
|
17
|
+
clearcallmanager()
|
|
18
|
+
} )
|
|
19
|
+
|
|
20
|
+
beforeEach( function() {
|
|
21
|
+
clearcallmanager()
|
|
22
|
+
} )
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
it( `Create call and send 183 - early`, async () => {
|
|
26
|
+
let srfscenario = new srf.srfscenario()
|
|
27
|
+
|
|
28
|
+
let rtpserver = callmanager.projectrtp.proxy.listen()
|
|
29
|
+
|
|
30
|
+
let connection = net.createConnection( 9002, "127.0.0.1" )
|
|
31
|
+
.on( "error", ( e ) => {
|
|
32
|
+
console.error( e )
|
|
33
|
+
} )
|
|
34
|
+
|
|
35
|
+
connection.on( "connect", () => {
|
|
36
|
+
/* announce our node */
|
|
37
|
+
connection.write( projectrtpmessage.createmessage( {"status":{"channel":{"available":5000,"current":0},"workercount":12,"instance":"ca0ef6a9-9174-444d-bdeb-4c9eb54d4566"}} ) )
|
|
38
|
+
} )
|
|
39
|
+
|
|
40
|
+
let msgid
|
|
41
|
+
let mixing
|
|
42
|
+
let messagestate = projectrtpmessage.newstate()
|
|
43
|
+
connection.on( "data", ( data ) => {
|
|
44
|
+
projectrtpmessage.parsemessage( messagestate, data, ( msg ) => {
|
|
45
|
+
if( "open" === msg.channel ) {
|
|
46
|
+
msgid = msg.id
|
|
47
|
+
setTimeout( () => connection.write( projectrtpmessage.createmessage( {"local":{"port":10008,"dtls":{"fingerprint":"Some fingerprint","enabled":false},"address":"192.168.0.141"},"id": msg.id, "uuid": uuidv4(),"action":"open","status":{"channel":{"available":4995,"current":5},"workercount":12,"instance":"ca0ef6a9-9174-444d-bdeb-4c9eb54d4566"}} ) ), 2 )
|
|
48
|
+
} else if ( "close" === msg.channel ) {
|
|
49
|
+
connection.write( projectrtpmessage.createmessage( {"id": msg.id,"uuid":msg.uuid,"action":"close","reason":"requested","stats":{"in":{"mos":4.5,"count":586,"dropped":0,"skip":0},"out":{"count":303,"skip":0},"tick":{"meanus":124,"maxus":508,"count":597}}} ) )
|
|
50
|
+
} else if ( "mix" === msg.channel ) {
|
|
51
|
+
mixing = true
|
|
52
|
+
}
|
|
53
|
+
} )
|
|
54
|
+
} )
|
|
55
|
+
|
|
56
|
+
/* ensure we are connected */
|
|
57
|
+
await new Promise( ( r ) => setTimeout( () => r(), 100 ) )
|
|
58
|
+
|
|
59
|
+
srfscenario.oncreateUAC( async ( contact, options, callbacks ) => {
|
|
60
|
+
callbacks.cbProvisional( {
|
|
61
|
+
"status": 183,
|
|
62
|
+
"msg": {
|
|
63
|
+
"body": `v=0
|
|
64
|
+
o=- 1608235282228 0 IN IP4 127.0.0.1
|
|
65
|
+
s=
|
|
66
|
+
c=IN IP4 192.168.0.141
|
|
67
|
+
t=0 0
|
|
68
|
+
m=audio 20000 RTP/AVP 0 101
|
|
69
|
+
a=rtpmap:101 telephone-event/8000
|
|
70
|
+
a=fmtp:101 0-16
|
|
71
|
+
a=sendrecv`.replace(/(\r\n|\n|\r)/gm, "\r\n")
|
|
72
|
+
}
|
|
73
|
+
} )
|
|
74
|
+
|
|
75
|
+
await new Promise( ( r ) => setTimeout( () => r(), 100 ) )
|
|
76
|
+
throw { "status": 503 }
|
|
77
|
+
} )
|
|
78
|
+
|
|
79
|
+
let msgsent
|
|
80
|
+
let call = await new Promise( ( resolve ) => {
|
|
81
|
+
srfscenario.oncall( async ( call ) => { resolve( call ) } )
|
|
82
|
+
srfscenario.inbound()
|
|
83
|
+
srfscenario.res.onsend( (c) => {
|
|
84
|
+
msgsent = c
|
|
85
|
+
})
|
|
86
|
+
} )
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
let newcall = await call.newuac( { "contact": "callto" } )
|
|
90
|
+
|
|
91
|
+
await call._onhangup( "wire" )
|
|
92
|
+
|
|
93
|
+
expect( newcall.state.early ).to.be.true
|
|
94
|
+
expect( call.state.early ).to.be.true
|
|
95
|
+
expect( mixing ).to.be.true
|
|
96
|
+
expect( msgsent ).to.equal( 183 )
|
|
97
|
+
|
|
98
|
+
connection.destroy()
|
|
99
|
+
rtpserver.destroy()
|
|
100
|
+
} )
|
|
101
|
+
|
|
102
|
+
} )
|
package/test/mock/srf.js
CHANGED