@babblevoice/projectrtp 2.6.0 → 2.6.2
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/Dockerfile +4 -4
- package/README.md +1 -1
- package/binding.gyp +3 -1
- package/lib/server.js +15 -10
- package/package.json +1 -1
- package/src/projectrtpchannel.cpp +46 -17
- package/src/projectrtpchannel.h +2 -0
- package/src/projectrtpchannelmux.cpp +17 -8
- package/src/projectrtpchannelmux.h +1 -0
- package/test/interface/projectrtpdtmf.js +61 -51
- package/test/interface/projectrtprecord.js +27 -31
- package/test/interface/rtpproxyserver.js +94 -67
package/Dockerfile
CHANGED
|
@@ -11,12 +11,12 @@ RUN apk add --no-cache \
|
|
|
11
11
|
cd libilbc-3.0.4; \
|
|
12
12
|
cmake . -DCMAKE_INSTALL_LIBDIR=/lib -DCMAKE_INSTALL_INCLUDEDIR=/usr/include; cmake --build .; cmake --install .;
|
|
13
13
|
|
|
14
|
-
WORKDIR /usr/
|
|
14
|
+
WORKDIR /usr/src/projectrtp
|
|
15
15
|
COPY . .
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
# a more correct to install this module into the global location
|
|
18
|
+
RUN npm install --production --build-from-source && \
|
|
19
|
+
npm install -g .
|
|
20
20
|
|
|
21
21
|
FROM alpine:3.21 AS app
|
|
22
22
|
|
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ If you wish to build outsode of a Docker image, there are npm target scripts for
|
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
68
|
docker buildx prune
|
|
69
|
-
docker buildx build --platform linux/amd64,linux/arm64 -t tinpotnick/projectrtp:2.
|
|
69
|
+
docker buildx build --platform linux/amd64,linux/arm64 -t tinpotnick/projectrtp:2.6.2 . --push
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
### Dev build
|
package/binding.gyp
CHANGED
package/lib/server.js
CHANGED
|
@@ -103,8 +103,9 @@ class channel {
|
|
|
103
103
|
openresolve
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
|
+
* listening or connection
|
|
106
107
|
* @private
|
|
107
|
-
* @type {
|
|
108
|
+
* @type { "l" | "c" }
|
|
108
109
|
*/
|
|
109
110
|
ctype
|
|
110
111
|
|
|
@@ -346,6 +347,7 @@ class channel {
|
|
|
346
347
|
const newchannel = new channel()
|
|
347
348
|
if( cb ) newchannel.em.on( "all", cb )
|
|
348
349
|
|
|
350
|
+
newchannel.ctype = this.ctype
|
|
349
351
|
newchannel.connection = this.connection
|
|
350
352
|
newchannel.channels = this.channels
|
|
351
353
|
newchannel.channels.push( newchannel )
|
|
@@ -541,7 +543,7 @@ class channel {
|
|
|
541
543
|
}
|
|
542
544
|
|
|
543
545
|
/**
|
|
544
|
-
* We have received
|
|
546
|
+
* We have received the open confirm message so we should action it
|
|
545
547
|
* @private
|
|
546
548
|
* @param { object } msg
|
|
547
549
|
* @returns { boolean } - is further processing required
|
|
@@ -592,14 +594,17 @@ class channel {
|
|
|
592
594
|
}
|
|
593
595
|
|
|
594
596
|
#cleanupsharedchannels() {
|
|
595
|
-
if( this.channels )
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
597
|
+
if( !this.channels ) return
|
|
598
|
+
|
|
599
|
+
// adjust with filter
|
|
600
|
+
const index = this.channels.indexOf( this )
|
|
601
|
+
if( -1 < index ) this.channels.splice( index, 1 )
|
|
602
|
+
|
|
603
|
+
if( "c" !== this.ctype ) return
|
|
604
|
+
if( 0 !== this.channels.length ) return
|
|
605
|
+
if( !this.connection.sock ) return
|
|
606
|
+
|
|
607
|
+
this.connection.sock.destroy()
|
|
603
608
|
}
|
|
604
609
|
|
|
605
610
|
#haserror( msg ) {
|
package/package.json
CHANGED
|
@@ -83,6 +83,7 @@ projectrtpchannel::projectrtpchannel( unsigned short port ):
|
|
|
83
83
|
ssrcin( 0 ),
|
|
84
84
|
ssrcout( 0 ),
|
|
85
85
|
tsout( 0 ),
|
|
86
|
+
eventtsout( 0 ),
|
|
86
87
|
snout( 0 ),
|
|
87
88
|
send( true ),
|
|
88
89
|
recv( true ),
|
|
@@ -710,20 +711,33 @@ bool projectrtpchannel::recordercompleted( const channelrecorder::pointer& rec )
|
|
|
710
711
|
}
|
|
711
712
|
|
|
712
713
|
/**
|
|
713
|
-
*
|
|
714
|
-
*
|
|
714
|
+
* Helper function for checkfordtmf -
|
|
715
|
+
* 1. stop player if set to stop on event
|
|
716
|
+
* 2. signal back to our control server that an event has been received.
|
|
717
|
+
* 3. forward to other channels we are mixing with
|
|
715
718
|
*/
|
|
716
719
|
static char dtmfchars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '#', 'A', 'B', 'C', 'D', 'F' };
|
|
717
720
|
void projectrtpchannel::sendtelevent( void ) {
|
|
718
721
|
auto self = shared_from_this();
|
|
719
|
-
SpinLockGuard guard( this->playerlock );
|
|
720
722
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
723
|
+
{
|
|
724
|
+
SpinLockGuard guard( this->playerlock );
|
|
725
|
+
|
|
726
|
+
if( this->player && this->player->doesinterupt() ) {
|
|
727
|
+
postdatabacktojsfromthread( self, "play", "end", "telephone-event" );
|
|
728
|
+
this->player = nullptr;
|
|
729
|
+
}
|
|
724
730
|
}
|
|
725
731
|
|
|
726
732
|
postdatabacktojsfromthread( self, "telephone-event", std::string( 1, dtmfchars[ this->lasttelephoneevent ] ) );
|
|
733
|
+
|
|
734
|
+
if( !this->mixing ) return;
|
|
735
|
+
|
|
736
|
+
{
|
|
737
|
+
SpinLockGuard guard( this->mixerlock );
|
|
738
|
+
this->mixer->senddtmf( self, dtmfchars[ this->lasttelephoneevent ] );
|
|
739
|
+
}
|
|
740
|
+
|
|
727
741
|
}
|
|
728
742
|
/*
|
|
729
743
|
## checkfordtmf
|
|
@@ -1233,19 +1247,26 @@ void projectrtpchannel::dounmix( void ) {
|
|
|
1233
1247
|
}
|
|
1234
1248
|
}
|
|
1235
1249
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
*/
|
|
1250
|
+
/**
|
|
1251
|
+
* Queue digits to send as RFC 2833.
|
|
1252
|
+
*/
|
|
1240
1253
|
void projectrtpchannel::dtmf( std::string digits ) {
|
|
1241
1254
|
SpinLockGuard guard( this->queuddigitslock );
|
|
1242
1255
|
this->queueddigits += digits;
|
|
1243
1256
|
}
|
|
1244
1257
|
|
|
1245
|
-
|
|
1258
|
+
/**
|
|
1259
|
+
* Queue digit to send as RFC 2833.
|
|
1260
|
+
*/
|
|
1261
|
+
void projectrtpchannel::dtmf( char digit ) {
|
|
1262
|
+
SpinLockGuard guard( this->queuddigitslock );
|
|
1263
|
+
this->queueddigits += digit;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
const char volume = 13;
|
|
1246
1267
|
const char endofevent = 0x80;
|
|
1247
1268
|
|
|
1248
|
-
const int numevents =
|
|
1269
|
+
const int numevents = 10;
|
|
1249
1270
|
const int numendevents = 3;
|
|
1250
1271
|
|
|
1251
1272
|
/*
|
|
@@ -1265,9 +1286,7 @@ void projectrtpchannel::senddtmf( void ) {
|
|
|
1265
1286
|
}
|
|
1266
1287
|
}
|
|
1267
1288
|
|
|
1268
|
-
if( 0 == tosend )
|
|
1269
|
-
return;
|
|
1270
|
-
}
|
|
1289
|
+
if( 0 == tosend ) return;
|
|
1271
1290
|
|
|
1272
1291
|
/*
|
|
1273
1292
|
RFC 2833:
|
|
@@ -1297,9 +1316,18 @@ void projectrtpchannel::senddtmf( void ) {
|
|
|
1297
1316
|
|
|
1298
1317
|
if( this->dtmfsendcount <= numevents ) {
|
|
1299
1318
|
rtppacket *dst = this->gettempoutbuf();
|
|
1319
|
+
/* the timestamp is fixed to the start of the event */
|
|
1320
|
+
if( 0 == this->dtmfsendcount ) {
|
|
1321
|
+
this->eventtsout = this->tsout;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
dst->settimestamp( this->eventtsout );
|
|
1300
1325
|
dst->setpayloadtype( this->rfc2833pt );
|
|
1301
1326
|
dst->setpayloadlength( 4 );
|
|
1302
|
-
uint8_t *pl =
|
|
1327
|
+
uint8_t *pl = dst->getpayload();
|
|
1328
|
+
|
|
1329
|
+
dst->setmarker( 0 == this->dtmfsendcount );
|
|
1330
|
+
|
|
1303
1331
|
pl[ 0 ] = tosend;
|
|
1304
1332
|
pl[ 1 ] = volume; /* end of event & reserved & volume */
|
|
1305
1333
|
uint16_t *tmp = ( uint16_t * ) &pl[ 2 ]; /* event duration */
|
|
@@ -1310,8 +1338,9 @@ void projectrtpchannel::senddtmf( void ) {
|
|
|
1310
1338
|
} else {
|
|
1311
1339
|
/* end packet */
|
|
1312
1340
|
rtppacket *dst = this->gettempoutbuf();
|
|
1341
|
+
|
|
1342
|
+
dst->settimestamp( this->eventtsout );
|
|
1313
1343
|
dst->setpayloadtype( this->rfc2833pt );
|
|
1314
|
-
dst->setmarker();
|
|
1315
1344
|
dst->setpayloadlength( 4 );
|
|
1316
1345
|
uint8_t *pl = dst->getpayload();
|
|
1317
1346
|
pl[ 0 ] = tosend;
|
package/src/projectrtpchannel.h
CHANGED
|
@@ -114,12 +114,14 @@ public:
|
|
|
114
114
|
bool mix( projectrtpchannel::pointer other );
|
|
115
115
|
bool unmix( void );
|
|
116
116
|
void dtmf( std::string digits );
|
|
117
|
+
void dtmf( char digit );
|
|
117
118
|
rtppacket *gettempoutbuf( void );
|
|
118
119
|
|
|
119
120
|
std::atomic_uint32_t codec;
|
|
120
121
|
uint32_t ssrcin;
|
|
121
122
|
uint32_t ssrcout;
|
|
122
123
|
uint32_t tsout;
|
|
124
|
+
uint32_t eventtsout;
|
|
123
125
|
uint16_t snout;
|
|
124
126
|
|
|
125
127
|
/* do we send, do we receive */
|
|
@@ -85,11 +85,6 @@ void projectchannelmux::mixall( void ) {
|
|
|
85
85
|
|
|
86
86
|
if( !chan->checkfordtmf( src ) ) break;
|
|
87
87
|
|
|
88
|
-
for( auto& dtmfchan: this->channels ) {
|
|
89
|
-
if( dtmfchan->recv && chan.get() != dtmfchan.get() ) {
|
|
90
|
-
this->postrtpdata( chan, dtmfchan, src );
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
88
|
/* remove the DTMF packet */
|
|
94
89
|
{
|
|
95
90
|
SpinLockGuard guard( chan->rtpbufferlock );
|
|
@@ -175,8 +170,8 @@ void projectchannelmux::mix2( void ) {
|
|
|
175
170
|
}
|
|
176
171
|
}
|
|
177
172
|
|
|
173
|
+
/* process but ignore and look for next other packet */
|
|
178
174
|
if( !chan1->checkfordtmf( src ) ) break;
|
|
179
|
-
this->postrtpdata( chan1, chan2, src );
|
|
180
175
|
}
|
|
181
176
|
this->postrtpdata( chan1, chan2, src );
|
|
182
177
|
|
|
@@ -205,11 +200,25 @@ void projectchannelmux::mix2( void ) {
|
|
|
205
200
|
}
|
|
206
201
|
|
|
207
202
|
if( !chan2->checkfordtmf( src ) ) break;
|
|
208
|
-
this->postrtpdata( chan2, chan1, src );
|
|
209
203
|
}
|
|
204
|
+
|
|
210
205
|
this->postrtpdata( chan2, chan1, src );
|
|
211
206
|
}
|
|
212
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Called from a channel which has received DTMF - we forward onto all other channels,
|
|
210
|
+
* this is likely to have been called from mix2 and mixall. NB this only queues
|
|
211
|
+
* the digit on the outbound - we have to actuall send them as well.
|
|
212
|
+
*/
|
|
213
|
+
void projectchannelmux::senddtmf( projectrtpchannelptr from, char digit ) {
|
|
214
|
+
for( auto& chan: this->channels ) {
|
|
215
|
+
if( chan.get() == from.get() ) continue;
|
|
216
|
+
if( !chan->recv ) continue;
|
|
217
|
+
|
|
218
|
+
chan->dtmf( digit );
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
213
222
|
bool projectchannelmux::channelremoverequested( const projectrtpchannelptr& chan ) {
|
|
214
223
|
if( chan->removemixer || chan->_requestclose || (!chan->active) ) {
|
|
215
224
|
chan->dounmix();
|
|
@@ -267,7 +276,7 @@ void projectchannelmux::handletick( const boost::system::error_code& error ) {
|
|
|
267
276
|
|
|
268
277
|
for( auto& chan: workingchannels ) {
|
|
269
278
|
chan->senddtmf();
|
|
270
|
-
chan->writerecordings();
|
|
279
|
+
chan->writerecordings();
|
|
271
280
|
chan->checkidlerecv(); // this can call doclose() - recorders might be destroyed now
|
|
272
281
|
chan->endticktimer();
|
|
273
282
|
|
|
@@ -35,6 +35,7 @@ public:
|
|
|
35
35
|
inline size_t size() { return this->channels.size(); }
|
|
36
36
|
void addchannel( projectrtpchannelptr chan );
|
|
37
37
|
void addchannels( projectrtpchannelptr chana, projectrtpchannelptr chanb );
|
|
38
|
+
void senddtmf( projectrtpchannelptr from, char digit );
|
|
38
39
|
void go( void );
|
|
39
40
|
|
|
40
41
|
private:
|
|
@@ -5,6 +5,11 @@ const dgram = require( "dgram" )
|
|
|
5
5
|
|
|
6
6
|
const pcap = require( "./pcap" )
|
|
7
7
|
|
|
8
|
+
const pkcountperdtmfdigit = 14 /* defined in projectrtpchannel::senddtmf */
|
|
9
|
+
const pausebetweendtmfdigits = 200 /* mS 10 pks ref projectrtpchannel::senddtmf */
|
|
10
|
+
const timeperdtmfdigit = ( pkcountperdtmfdigit * 20 /* mS */ ) + pausebetweendtmfdigits
|
|
11
|
+
const dtmfeventduration = ( ( pkcountperdtmfdigit - 2) * 160 )
|
|
12
|
+
|
|
8
13
|
/*
|
|
9
14
|
i.e. the RTP payload
|
|
10
15
|
str = "80 e5 03 b5 00 02 44 a0 1e e3 61 fb 03 0a 00 a0 4c d1"
|
|
@@ -168,9 +173,13 @@ describe( "dtmf", function() {
|
|
|
168
173
|
} )
|
|
169
174
|
|
|
170
175
|
|
|
171
|
-
it( "single channel and request rtp server to send 2833", function(
|
|
176
|
+
it( "single channel and request rtp server to send 2833", async function() {
|
|
172
177
|
/* TODO - check tre fuller structure of an RTP event (duration, volume, etc) */
|
|
173
178
|
/* create our RTP/UDP endpoint */
|
|
179
|
+
|
|
180
|
+
let done
|
|
181
|
+
const finished = new Promise( resolve => done = resolve )
|
|
182
|
+
|
|
174
183
|
const server = dgram.createSocket( "udp4" )
|
|
175
184
|
let dtmfpkcount = 0
|
|
176
185
|
server.on( "message", function( msg ) {
|
|
@@ -182,41 +191,46 @@ describe( "dtmf", function() {
|
|
|
182
191
|
}
|
|
183
192
|
} )
|
|
184
193
|
|
|
185
|
-
this.timeout(
|
|
186
|
-
this.slow(
|
|
194
|
+
this.timeout( 5000 )
|
|
195
|
+
this.slow( 4000 )
|
|
187
196
|
|
|
188
197
|
server.bind()
|
|
189
|
-
server.on( "listening",
|
|
198
|
+
await new Promise( resolve => server.on( "listening", resolve ) )
|
|
190
199
|
|
|
191
|
-
|
|
200
|
+
const ourport = server.address().port
|
|
192
201
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
202
|
+
let expectedmessagecount = 0
|
|
203
|
+
const expectedmessages = [
|
|
204
|
+
{ action: "close" }
|
|
205
|
+
]
|
|
197
206
|
|
|
198
|
-
|
|
207
|
+
const channel = await projectrtp.openchannel( { "remote": { "address": "localhost", "port": ourport, "codec": 0 } }, function( d ) {
|
|
199
208
|
|
|
200
|
-
|
|
201
|
-
|
|
209
|
+
expect( d ).to.deep.include( expectedmessages[ expectedmessagecount ] )
|
|
210
|
+
expectedmessagecount++
|
|
202
211
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
} )
|
|
212
|
+
if( "close" === d.action ) {
|
|
213
|
+
server.close()
|
|
214
|
+
done()
|
|
215
|
+
}
|
|
216
|
+
} )
|
|
209
217
|
|
|
210
|
-
|
|
218
|
+
expect( channel.echo() ).to.be.true
|
|
211
219
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
220
|
+
/* send a packet every 20mS x 50 */
|
|
221
|
+
for( let i = 0; 50 > i; i ++ ) {
|
|
222
|
+
sendpk( i, i*160, i*20, channel.local.port, server )
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
await new Promise( resolve => setTimeout( resolve, 400 ) )
|
|
226
|
+
channel.dtmf( "#1" )
|
|
227
|
+
|
|
228
|
+
await new Promise( resolve => setTimeout( resolve, 1000 ) )
|
|
229
|
+
channel.close()
|
|
230
|
+
await finished
|
|
231
|
+
|
|
232
|
+
expect( dtmfpkcount ).to.equal( 2*pkcountperdtmfdigit )
|
|
216
233
|
|
|
217
|
-
setTimeout( () => channel.dtmf( "#1" ), 400 )
|
|
218
|
-
setTimeout( () => channel.close(), 1000 )
|
|
219
|
-
} )
|
|
220
234
|
} )
|
|
221
235
|
|
|
222
236
|
|
|
@@ -274,7 +288,7 @@ describe( "dtmf", function() {
|
|
|
274
288
|
|
|
275
289
|
await new Promise( ( resolve ) => { setTimeout( () => resolve(), 400 ) } )
|
|
276
290
|
channela.dtmf( "*9F" )
|
|
277
|
-
await new Promise( ( resolve ) => { setTimeout( () => resolve(),
|
|
291
|
+
await new Promise( ( resolve ) => { setTimeout( () => resolve(), 3 * timeperdtmfdigit ) } )
|
|
278
292
|
channela.close()
|
|
279
293
|
|
|
280
294
|
await finished
|
|
@@ -282,18 +296,14 @@ describe( "dtmf", function() {
|
|
|
282
296
|
clienta.close()
|
|
283
297
|
clientb.close()
|
|
284
298
|
|
|
285
|
-
expect( dtmfpks.length ).to.equal( 3*
|
|
299
|
+
expect( dtmfpks.length ).to.equal( 3*pkcountperdtmfdigit )
|
|
286
300
|
expect( dtmfpks[ 0 ].event.id ).to.equal( 10 )
|
|
287
301
|
expect( dtmfpks[ 0 ].event.duration ).to.equal( 160 )
|
|
288
302
|
expect( dtmfpks[ 0 ].event.eoe ).to.be.false
|
|
289
303
|
|
|
290
|
-
expect( dtmfpks[
|
|
291
|
-
expect( dtmfpks[
|
|
292
|
-
expect( dtmfpks[
|
|
293
|
-
|
|
294
|
-
expect( dtmfpks[ 6 ].event.id ).to.equal( 10 )
|
|
295
|
-
expect( dtmfpks[ 6 ].event.duration ).to.equal( 800 )
|
|
296
|
-
expect( dtmfpks[ 6 ].event.eoe ).to.be.true
|
|
304
|
+
expect( dtmfpks[ pkcountperdtmfdigit - 3 ].event.id ).to.equal( 10 )
|
|
305
|
+
expect( dtmfpks[ pkcountperdtmfdigit - 3 ].event.duration ).to.equal( dtmfeventduration )
|
|
306
|
+
expect( dtmfpks[ pkcountperdtmfdigit - 3 ].event.eoe ).to.be.true
|
|
297
307
|
|
|
298
308
|
} )
|
|
299
309
|
|
|
@@ -354,7 +364,7 @@ describe( "dtmf", function() {
|
|
|
354
364
|
|
|
355
365
|
await new Promise( ( resolve ) => { setTimeout( () => resolve(), 400 ) } )
|
|
356
366
|
channela.dtmf( "*9F" )
|
|
357
|
-
await new Promise( ( resolve ) => { setTimeout( () => resolve(),
|
|
367
|
+
await new Promise( ( resolve ) => { setTimeout( () => resolve(), 3 * timeperdtmfdigit ) } )
|
|
358
368
|
channela.close()
|
|
359
369
|
|
|
360
370
|
await finished
|
|
@@ -362,7 +372,7 @@ describe( "dtmf", function() {
|
|
|
362
372
|
clienta.close()
|
|
363
373
|
clientb.close()
|
|
364
374
|
|
|
365
|
-
expect( dtmfpkcount ).to.equal( 3*
|
|
375
|
+
expect( dtmfpkcount ).to.equal( 3*pkcountperdtmfdigit )
|
|
366
376
|
} )
|
|
367
377
|
|
|
368
378
|
it( "3 channels mixing and request rtp server to send 2833 to one", async function() {
|
|
@@ -436,14 +446,14 @@ describe( "dtmf", function() {
|
|
|
436
446
|
|
|
437
447
|
await new Promise( ( resolve ) => { setTimeout( () => resolve(), 100 ) } )
|
|
438
448
|
channela.dtmf( "*9ABD" )
|
|
439
|
-
await new Promise( ( resolve ) => { setTimeout( () => resolve(),
|
|
449
|
+
await new Promise( ( resolve ) => { setTimeout( () => resolve(), 5 * timeperdtmfdigit ) } )
|
|
440
450
|
channela.close()
|
|
441
451
|
|
|
442
452
|
clienta.close()
|
|
443
453
|
clientb.close()
|
|
444
454
|
clientc.close()
|
|
445
455
|
|
|
446
|
-
expect( dtmfpks.length ).to.equal( 5*
|
|
456
|
+
expect( dtmfpks.length ).to.equal( 5*pkcountperdtmfdigit )
|
|
447
457
|
|
|
448
458
|
await finished
|
|
449
459
|
} )
|
|
@@ -721,9 +731,9 @@ describe( "dtmf", function() {
|
|
|
721
731
|
|
|
722
732
|
await finished
|
|
723
733
|
|
|
734
|
+
expect( dtmfpkcount ).to.be.within( ( pkcountperdtmfdigit * 2 ) - 4 , pkcountperdtmfdigit * 2 ) /* allow loss */
|
|
724
735
|
expect( endpointapkcount ).to.be.within( 30, 51 )
|
|
725
|
-
expect( endpointbpkcount ).to.be.within( 30, 51 )
|
|
726
|
-
expect( dtmfpkcount ).to.be.within( 4, 8 )
|
|
736
|
+
expect( endpointbpkcount - dtmfpkcount ).to.be.within( 30, 51 )
|
|
727
737
|
|
|
728
738
|
expect( receivedmessages.length ).to.equal( 5 )
|
|
729
739
|
|
|
@@ -742,7 +752,7 @@ describe( "dtmf", function() {
|
|
|
742
752
|
} )
|
|
743
753
|
|
|
744
754
|
|
|
745
|
-
it( "mix 2 channels - pcmu <-> pcma and send DTMF different 2833 pt", async function() {
|
|
755
|
+
it( "wefwef mix 2 channels - pcmu <-> pcma and send DTMF different 2833 pt", async function() {
|
|
746
756
|
|
|
747
757
|
/*
|
|
748
758
|
When mixing 2 channels, we expect the second leg to receive the 2833 packets
|
|
@@ -864,11 +874,11 @@ describe( "dtmf", function() {
|
|
|
864
874
|
await finished
|
|
865
875
|
|
|
866
876
|
expect( endpointapkcount ).to.be.within( 30, 51 )
|
|
867
|
-
expect( endpointbpkcount ).to.be.within( 30, 51 )
|
|
877
|
+
expect( endpointbpkcount - dtmfpkcount ).to.be.within( 30, 51 )
|
|
868
878
|
|
|
869
879
|
expect( receivedmessages.length ).to.equal( 5 )
|
|
870
880
|
|
|
871
|
-
expect( dtmfpkcount ).to.be.within( 4,
|
|
881
|
+
expect( dtmfpkcount ).to.be.within( ( pkcountperdtmfdigit * 2 ) - 4 , pkcountperdtmfdigit * 2 ) /* allow loss */
|
|
872
882
|
|
|
873
883
|
expect( receivedmessages[ 0 ].action ).to.equal( "mix" )
|
|
874
884
|
expect( receivedmessages[ 1 ].action ).to.equal( "telephone-event" )
|
|
@@ -936,13 +946,13 @@ describe( "dtmf", function() {
|
|
|
936
946
|
} )
|
|
937
947
|
|
|
938
948
|
endpointa.bind()
|
|
939
|
-
await new Promise( (
|
|
949
|
+
await new Promise( ( resolve ) => { endpointa.on( "listening", function() { resolve() } ) } )
|
|
940
950
|
|
|
941
951
|
endpointb.bind()
|
|
942
|
-
await new Promise( (
|
|
952
|
+
await new Promise( ( resolve ) => { endpointb.on( "listening", function() { resolve() } ) } )
|
|
943
953
|
|
|
944
954
|
endpointc.bind()
|
|
945
|
-
await new Promise( (
|
|
955
|
+
await new Promise( ( resolve ) => { endpointc.on( "listening", function() { resolve() } ) } )
|
|
946
956
|
|
|
947
957
|
const receveiedmessages = []
|
|
948
958
|
|
|
@@ -1053,13 +1063,13 @@ describe( "dtmf", function() {
|
|
|
1053
1063
|
] )
|
|
1054
1064
|
|
|
1055
1065
|
expect( endpointapkcount ).to.be.within( 59, 70 )
|
|
1056
|
-
expect( endpointbpkcount ).to.be.within( 59, 70 )
|
|
1057
|
-
expect( endpointcpkcount ).to.be.within( 59, 70 )
|
|
1066
|
+
expect( endpointbpkcount - dtmfbpkcount ).to.be.within( 59, 70 )
|
|
1067
|
+
expect( endpointcpkcount - dtmfcpkcount ).to.be.within( 59, 70 )
|
|
1058
1068
|
|
|
1059
1069
|
// 3 after we return to the event loop and enter the callback with close event.
|
|
1060
1070
|
expect( dtmfapkcount ).to.equal( 0 )
|
|
1061
|
-
expect( dtmfbpkcount ).to.be.within( 4,
|
|
1062
|
-
expect( dtmfcpkcount ).to.be.within( 4,
|
|
1071
|
+
expect( dtmfbpkcount ).to.be.within( ( pkcountperdtmfdigit * 2 ) - 4, pkcountperdtmfdigit * 2 )
|
|
1072
|
+
expect( dtmfcpkcount ).to.be.within( ( pkcountperdtmfdigit * 2 ) - 4, pkcountperdtmfdigit * 2 )
|
|
1063
1073
|
|
|
1064
1074
|
expect( receveiedmessages[ 0 ].action ).to.equal( "mix" )
|
|
1065
1075
|
expect( receveiedmessages[ 1 ].action ).to.equal( "mix" )
|
|
@@ -407,11 +407,10 @@ describe( "record", function() {
|
|
|
407
407
|
this.slow( 7000 )
|
|
408
408
|
|
|
409
409
|
let done
|
|
410
|
-
const finished = new Promise( (
|
|
410
|
+
const finished = new Promise( ( resolve ) => done = resolve )
|
|
411
411
|
|
|
412
412
|
/* create our RTP/UDP endpoint */
|
|
413
413
|
const server = dgram.createSocket( "udp4" )
|
|
414
|
-
let channel
|
|
415
414
|
|
|
416
415
|
/* generate our data */
|
|
417
416
|
const startsilenceseconds = 1
|
|
@@ -430,44 +429,41 @@ describe( "record", function() {
|
|
|
430
429
|
] )
|
|
431
430
|
|
|
432
431
|
server.on( "message", function() {} )
|
|
433
|
-
|
|
434
432
|
server.bind()
|
|
433
|
+
await new Promise( resolve => server.on( "listening", resolve ) )
|
|
434
|
+
|
|
435
435
|
const receivedmessages = []
|
|
436
|
+
const channel = await prtp.projectrtp.openchannel( { "remote": { "address": "localhost", "port": server.address().port, "codec": 0 } }, function( d ) {
|
|
437
|
+
receivedmessages.push( d )
|
|
438
|
+
if( "close" !== d.action ) return
|
|
436
439
|
|
|
437
|
-
|
|
440
|
+
done()
|
|
441
|
+
} )
|
|
438
442
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
done()
|
|
443
|
-
}
|
|
444
|
-
} )
|
|
443
|
+
expect( channel.record( {
|
|
444
|
+
"file": "/tmp/dualrecording.wav"
|
|
445
|
+
} ) ).to.be.true
|
|
445
446
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
447
|
+
expect( channel.record( {
|
|
448
|
+
"file": "/tmp/dualrecordingpower.wav",
|
|
449
|
+
"startabovepower": 40,
|
|
450
|
+
"finishbelowpower": 200,
|
|
451
|
+
"minduration": 200,
|
|
452
|
+
"maxduration": 3500,
|
|
453
|
+
"poweraveragepackets": 10 /* faster response */
|
|
454
|
+
} ) ).to.be.true
|
|
449
455
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
"startabovepower": 40,
|
|
453
|
-
"finishbelowpower": 200,
|
|
454
|
-
"minduration": 200,
|
|
455
|
-
"maxduration": 3500,
|
|
456
|
-
"poweraveragepackets": 10 /* faster response */
|
|
457
|
-
} ) ).to.be.true
|
|
458
|
-
|
|
459
|
-
/* something to record */
|
|
460
|
-
expect( channel.echo() ).to.be.true
|
|
456
|
+
/* something to record */
|
|
457
|
+
expect( channel.echo() ).to.be.true
|
|
461
458
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
} )
|
|
459
|
+
for( let i = 0; i < 50*totalseconds; i ++ ) {
|
|
460
|
+
sendpk( i, i, channel.local.port, server, sendbuffer )
|
|
461
|
+
}
|
|
466
462
|
|
|
467
|
-
|
|
463
|
+
await new Promise( resolve => setTimeout( resolve, totalseconds * 1000 ) )
|
|
464
|
+
channel.close()
|
|
468
465
|
await finished
|
|
469
|
-
|
|
470
|
-
server.close()
|
|
466
|
+
await new Promise( resolve => server.close( () => resolve() ) )
|
|
471
467
|
|
|
472
468
|
let stats = fs.statSync( "/tmp/dualrecordingpower.wav" )
|
|
473
469
|
expect( stats.size ).to.be.within( 30000 , 41000 )
|
|
@@ -52,10 +52,15 @@ describe( "rtpproxy server", function() {
|
|
|
52
52
|
} )
|
|
53
53
|
|
|
54
54
|
let closereceived = false
|
|
55
|
-
n.setmessagehandler( "close", () => {
|
|
55
|
+
n.setmessagehandler( "close", ( msg ) => {
|
|
56
|
+
|
|
57
|
+
n.sendmessage( {
|
|
58
|
+
"action": "close",
|
|
59
|
+
"id": msg.id,
|
|
60
|
+
"uuid": msg.uuid
|
|
61
|
+
} )
|
|
62
|
+
|
|
56
63
|
closereceived = true
|
|
57
|
-
n.destroy()
|
|
58
|
-
p.destroy()
|
|
59
64
|
} )
|
|
60
65
|
|
|
61
66
|
const ourport = getnextport()
|
|
@@ -80,9 +85,10 @@ describe( "rtpproxy server", function() {
|
|
|
80
85
|
expect( channel.uuid ).that.is.a( "string" )
|
|
81
86
|
expect( channel.id ).that.is.a( "string" )
|
|
82
87
|
|
|
83
|
-
channel.close()
|
|
88
|
+
await channel.close()
|
|
84
89
|
|
|
85
|
-
|
|
90
|
+
n.destroy()
|
|
91
|
+
p.destroy()
|
|
86
92
|
|
|
87
93
|
expect( closereceived ).to.be.true
|
|
88
94
|
} )
|
|
@@ -108,12 +114,12 @@ describe( "rtpproxy server", function() {
|
|
|
108
114
|
receivedecho = true
|
|
109
115
|
} )
|
|
110
116
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
n.setmessagehandler( "close", ( msg ) => {
|
|
118
|
+
n.sendmessage( {
|
|
119
|
+
"action": "close",
|
|
120
|
+
"id": msg.id,
|
|
121
|
+
"uuid": msg.uuid
|
|
122
|
+
} )
|
|
117
123
|
} )
|
|
118
124
|
|
|
119
125
|
const ourport = getnextport()
|
|
@@ -121,10 +127,11 @@ describe( "rtpproxy server", function() {
|
|
|
121
127
|
await n.connect( ourport )
|
|
122
128
|
const channel = await prtp.openchannel()
|
|
123
129
|
channel.echo()
|
|
124
|
-
channel.close()
|
|
130
|
+
await channel.close()
|
|
131
|
+
|
|
132
|
+
n.destroy()
|
|
133
|
+
p.destroy()
|
|
125
134
|
|
|
126
|
-
/* this will only resolve when close received */
|
|
127
|
-
await closereceived
|
|
128
135
|
|
|
129
136
|
expect( receivedecho ).to.be.true
|
|
130
137
|
} )
|
|
@@ -154,12 +161,12 @@ describe( "rtpproxy server", function() {
|
|
|
154
161
|
expect( msg ).to.have.property( "digits" ).that.is.a( "string" ).to.equal( "#123" )
|
|
155
162
|
} )
|
|
156
163
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
164
|
+
n.setmessagehandler( "close", ( msg ) => {
|
|
165
|
+
n.sendmessage( {
|
|
166
|
+
"action": "close",
|
|
167
|
+
"id": msg.id,
|
|
168
|
+
"uuid": msg.uuid
|
|
169
|
+
} )
|
|
163
170
|
} )
|
|
164
171
|
|
|
165
172
|
const ourport = getnextport()
|
|
@@ -167,9 +174,10 @@ describe( "rtpproxy server", function() {
|
|
|
167
174
|
await n.connect( ourport )
|
|
168
175
|
const channel = await prtp.openchannel()
|
|
169
176
|
channel.dtmf( "#123" )
|
|
170
|
-
channel.close()
|
|
177
|
+
await channel.close()
|
|
171
178
|
|
|
172
|
-
|
|
179
|
+
n.destroy()
|
|
180
|
+
p.destroy()
|
|
173
181
|
|
|
174
182
|
expect( reveiveddtmf ).to.be.true
|
|
175
183
|
} )
|
|
@@ -197,12 +205,12 @@ describe( "rtpproxy server", function() {
|
|
|
197
205
|
let unmxmsg = {}
|
|
198
206
|
n.setmessagehandler( "unmix", ( msg ) => unmxmsg = msg )
|
|
199
207
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
208
|
+
n.setmessagehandler( "close", ( msg ) => {
|
|
209
|
+
n.sendmessage( {
|
|
210
|
+
"action": "close",
|
|
211
|
+
"id": msg.id,
|
|
212
|
+
"uuid": msg.uuid
|
|
213
|
+
} )
|
|
206
214
|
} )
|
|
207
215
|
|
|
208
216
|
const ourport = getnextport()
|
|
@@ -213,10 +221,13 @@ describe( "rtpproxy server", function() {
|
|
|
213
221
|
channela.mix( channelb )
|
|
214
222
|
channela.unmix()
|
|
215
223
|
|
|
216
|
-
|
|
217
|
-
|
|
224
|
+
await Promise.all( [
|
|
225
|
+
channela.close(),
|
|
226
|
+
channelb.close()
|
|
227
|
+
] )
|
|
218
228
|
|
|
219
|
-
|
|
229
|
+
n.destroy()
|
|
230
|
+
p.destroy()
|
|
220
231
|
|
|
221
232
|
expect( mxmsg ).to.have.property( "channel" ).that.is.a( "string" ).to.equal( "mix" )
|
|
222
233
|
expect( mxmsg ).to.have.property( "other" ).that.is.a( "object" )
|
|
@@ -250,7 +261,13 @@ describe( "rtpproxy server", function() {
|
|
|
250
261
|
} )
|
|
251
262
|
} )
|
|
252
263
|
|
|
253
|
-
n.setmessagehandler( "close", () => {
|
|
264
|
+
n.setmessagehandler( "close", ( msg ) => {
|
|
265
|
+
n.sendmessage( {
|
|
266
|
+
"action": "close",
|
|
267
|
+
"id": msg.id,
|
|
268
|
+
"uuid": msg.uuid
|
|
269
|
+
} )
|
|
270
|
+
} )
|
|
254
271
|
|
|
255
272
|
n2.setmessagehandler( "open", ( msg ) => {
|
|
256
273
|
n2.sendmessage( {
|
|
@@ -266,7 +283,13 @@ describe( "rtpproxy server", function() {
|
|
|
266
283
|
|
|
267
284
|
} )
|
|
268
285
|
|
|
269
|
-
n2.setmessagehandler( "close", () => {
|
|
286
|
+
n2.setmessagehandler( "close", ( msg ) => {
|
|
287
|
+
n.sendmessage( {
|
|
288
|
+
"action": "close",
|
|
289
|
+
"id": msg.id,
|
|
290
|
+
"uuid": msg.uuid
|
|
291
|
+
} )
|
|
292
|
+
} )
|
|
270
293
|
|
|
271
294
|
const ourport = getnextport()
|
|
272
295
|
const p = await prtp.proxy.listen( undefined, "127.0.0.1", ourport )
|
|
@@ -276,10 +299,11 @@ describe( "rtpproxy server", function() {
|
|
|
276
299
|
const channel1 = await prtp.openchannel()
|
|
277
300
|
const channel2 = await prtp.openchannel()
|
|
278
301
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
302
|
+
await Promise.all( [
|
|
303
|
+
channel1.close(),
|
|
304
|
+
channel2.close()
|
|
305
|
+
] )
|
|
306
|
+
|
|
283
307
|
|
|
284
308
|
n.destroy()
|
|
285
309
|
n2.destroy()
|
|
@@ -314,12 +338,12 @@ describe( "rtpproxy server", function() {
|
|
|
314
338
|
remotereceived = true
|
|
315
339
|
} )
|
|
316
340
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
341
|
+
n.setmessagehandler( "close", ( msg ) => {
|
|
342
|
+
n.sendmessage( {
|
|
343
|
+
"action": "close",
|
|
344
|
+
"id": msg.id,
|
|
345
|
+
"uuid": msg.uuid
|
|
346
|
+
} )
|
|
323
347
|
} )
|
|
324
348
|
|
|
325
349
|
const ourport = getnextport()
|
|
@@ -328,8 +352,9 @@ describe( "rtpproxy server", function() {
|
|
|
328
352
|
const channel = await prtp.openchannel()
|
|
329
353
|
channel.remote( "wouldbearemoteobject" )
|
|
330
354
|
|
|
331
|
-
channel.close()
|
|
332
|
-
|
|
355
|
+
await channel.close()
|
|
356
|
+
n.destroy()
|
|
357
|
+
p.destroy()
|
|
333
358
|
|
|
334
359
|
expect( remotereceived ).to.be.true
|
|
335
360
|
} )
|
|
@@ -370,11 +395,15 @@ describe( "rtpproxy server", function() {
|
|
|
370
395
|
let recmsg
|
|
371
396
|
n.setmessagehandler( "record", ( msg ) => {
|
|
372
397
|
recmsg = msg
|
|
373
|
-
|
|
398
|
+
done()
|
|
374
399
|
} )
|
|
375
400
|
|
|
376
|
-
n.setmessagehandler( "close", () => {
|
|
377
|
-
|
|
401
|
+
n.setmessagehandler( "close", ( msg ) => {
|
|
402
|
+
n.sendmessage( {
|
|
403
|
+
"action": "close",
|
|
404
|
+
"id": msg.id,
|
|
405
|
+
"uuid": msg.uuid
|
|
406
|
+
} )
|
|
378
407
|
} )
|
|
379
408
|
|
|
380
409
|
const ourport = getnextport()
|
|
@@ -384,6 +413,7 @@ describe( "rtpproxy server", function() {
|
|
|
384
413
|
channel.play( "wouldbeaplayobject" )
|
|
385
414
|
|
|
386
415
|
await completed
|
|
416
|
+
await channel.close()
|
|
387
417
|
|
|
388
418
|
n.destroy()
|
|
389
419
|
p.destroy()
|
|
@@ -400,7 +430,7 @@ describe( "rtpproxy server", function() {
|
|
|
400
430
|
expect( playmsg ).to.have.property( "uuid" ).that.is.a( "string" )
|
|
401
431
|
expect( playmsg ).to.have.property( "soup" ).that.is.a( "string" ).to.equal( "wouldbeaplayobject" )
|
|
402
432
|
|
|
403
|
-
expect( channel.history ).to.be.an( "array" ).to.have.length(
|
|
433
|
+
expect( channel.history ).to.be.an( "array" ).to.have.length( 7 )
|
|
404
434
|
|
|
405
435
|
} )
|
|
406
436
|
|
|
@@ -437,12 +467,17 @@ describe( "rtpproxy server", function() {
|
|
|
437
467
|
"uuid": msg.uuid
|
|
438
468
|
} )
|
|
439
469
|
|
|
440
|
-
setTimeout( () => channel.close(), 10 )
|
|
441
|
-
} )
|
|
442
|
-
n.setmessagehandler( "close", () => {
|
|
443
470
|
done()
|
|
444
471
|
} )
|
|
445
472
|
|
|
473
|
+
n.setmessagehandler( "close", ( msg ) => {
|
|
474
|
+
n.sendmessage( {
|
|
475
|
+
"action": "close",
|
|
476
|
+
"id": msg.id,
|
|
477
|
+
"uuid": msg.uuid
|
|
478
|
+
} )
|
|
479
|
+
} )
|
|
480
|
+
|
|
446
481
|
const ourport = getnextport()
|
|
447
482
|
const p = await prtp.proxy.listen( undefined, "127.0.0.1", ourport )
|
|
448
483
|
await n.connect( ourport )
|
|
@@ -450,6 +485,7 @@ describe( "rtpproxy server", function() {
|
|
|
450
485
|
channel.direction( { send: false, recv: false } )
|
|
451
486
|
|
|
452
487
|
await completed
|
|
488
|
+
await channel.close()
|
|
453
489
|
|
|
454
490
|
n.destroy()
|
|
455
491
|
p.destroy()
|
|
@@ -482,16 +518,12 @@ describe( "rtpproxy server", function() {
|
|
|
482
518
|
} )
|
|
483
519
|
} )
|
|
484
520
|
|
|
485
|
-
let closeresolv
|
|
486
|
-
const closepromise = new Promise( ( r ) => closeresolv = r )
|
|
487
521
|
n.setmessagehandler( "close", ( msg, sendmessage ) => {
|
|
488
522
|
sendmessage( {
|
|
489
523
|
"action": "close",
|
|
490
524
|
"uuid": msg.uuid,
|
|
491
525
|
"id": msg.id
|
|
492
526
|
} )
|
|
493
|
-
|
|
494
|
-
closeresolv()
|
|
495
527
|
} )
|
|
496
528
|
|
|
497
529
|
const chnl = await prtp.openchannel()
|
|
@@ -500,9 +532,7 @@ describe( "rtpproxy server", function() {
|
|
|
500
532
|
expect( chnl ).to.have.property( "local" ).that.is.a( "object" )
|
|
501
533
|
expect( chnl.local ).to.have.property( "port" ).that.is.a( "number" )
|
|
502
534
|
expect( chnl.local ).to.have.property( "address" ).that.is.a( "string" )
|
|
503
|
-
chnl.close()
|
|
504
|
-
|
|
505
|
-
await closepromise
|
|
535
|
+
await chnl.close()
|
|
506
536
|
|
|
507
537
|
n.destroy()
|
|
508
538
|
|
|
@@ -554,15 +584,13 @@ describe( "rtpproxy server", function() {
|
|
|
554
584
|
} )
|
|
555
585
|
} )
|
|
556
586
|
|
|
557
|
-
|
|
558
|
-
const closepromise = new Promise( ( r ) => closeresolv = r )
|
|
587
|
+
|
|
559
588
|
n.setmessagehandler( "close", ( msg, sendmessage ) => {
|
|
560
589
|
sendmessage( {
|
|
561
590
|
"action": "close",
|
|
562
591
|
"uuid": msg.uuid,
|
|
563
592
|
"id": msg.id
|
|
564
593
|
} )
|
|
565
|
-
closeresolv()
|
|
566
594
|
} )
|
|
567
595
|
|
|
568
596
|
n2.setmessagehandler( "close", ( msg, sendmessage ) => {
|
|
@@ -571,7 +599,6 @@ describe( "rtpproxy server", function() {
|
|
|
571
599
|
"uuid": msg.uuid,
|
|
572
600
|
"id": msg.id
|
|
573
601
|
} )
|
|
574
|
-
closeresolv()
|
|
575
602
|
} )
|
|
576
603
|
|
|
577
604
|
const chnl = await prtp.openchannel()
|
|
@@ -579,10 +606,10 @@ describe( "rtpproxy server", function() {
|
|
|
579
606
|
|
|
580
607
|
expect( chnl.connection ).to.be.equal( chnl2.connection )
|
|
581
608
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
609
|
+
Promise.all( [
|
|
610
|
+
chnl.close(),
|
|
611
|
+
chnl2.close()
|
|
612
|
+
] )
|
|
586
613
|
|
|
587
614
|
n.destroy()
|
|
588
615
|
n2.destroy()
|