forked from rtc-io/rtc-tools
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcouple.js
224 lines (170 loc) · 6.21 KB
/
couple.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/* jshint node: true */
'use strict';
var mbus = require('mbus');
var queue = require('rtc-taskqueue');
var cleanup = require('./cleanup');
var monitor = require('./monitor');
var throttle = require('cog/throttle');
var pluck = require('whisk/pluck');
var pluckCandidate = pluck('candidate', 'sdpMid', 'sdpMLineIndex');
var CLOSED_STATES = [ 'closed', 'failed' ];
var CHECKING_STATES = [ 'checking' ];
/**
### rtc-tools/couple
#### couple(pc, targetId, signaller, opts?)
Couple a WebRTC connection with another webrtc connection identified by
`targetId` via the signaller.
The following options can be provided in the `opts` argument:
- `sdpfilter` (default: null)
A simple function for filtering SDP as part of the peer
connection handshake (see the Using Filters details below).
##### Example Usage
```js
var couple = require('rtc/couple');
couple(pc, '54879965-ce43-426e-a8ef-09ac1e39a16d', signaller);
```
##### Using Filters
In certain instances you may wish to modify the raw SDP that is provided
by the `createOffer` and `createAnswer` calls. This can be done by passing
a `sdpfilter` function (or array) in the options. For example:
```js
// run the sdp from through a local tweakSdp function.
couple(pc, '54879965-ce43-426e-a8ef-09ac1e39a16d', signaller, {
sdpfilter: tweakSdp
});
```
**/
function couple(pc, targetId, signaller, opts) {
var debugLabel = (opts || {}).debugLabel || 'rtc';
var debug = require('cog/logger')(debugLabel + '/couple');
// create a monitor for the connection
var mon = monitor(pc, targetId, signaller, (opts || {}).logger);
var emit = mbus('', mon);
var reactive = (opts || {}).reactive;
var endOfCandidates = true;
// configure the time to wait between receiving a 'disconnect'
// iceConnectionState and determining that we are closed
var disconnectTimeout = (opts || {}).disconnectTimeout || 10000;
var disconnectTimer;
// initilaise the negotiation helpers
var isMaster = signaller.isMaster(targetId);
// initialise the processing queue (one at a time please)
var q = queue(pc, opts);
var createOrRequestOffer = throttle(function() {
if (! isMaster) {
return signaller.to(targetId).send('/negotiate');
}
q.createOffer();
}, 100, { leading: false });
var debounceOffer = throttle(q.createOffer, 100, { leading: false });
function decouple() {
debug('decoupling ' + signaller.id + ' from ' + targetId);
// stop the monitor
// mon.removeAllListeners();
mon.stop();
// cleanup the peerconnection
cleanup(pc);
// remove listeners
signaller.removeListener('sdp', handleSdp);
signaller.removeListener('candidate', handleCandidate);
signaller.removeListener('negotiate', handleNegotiateRequest);
// remove listeners (version >= 5)
signaller.removeListener('message:sdp', handleSdp);
signaller.removeListener('message:candidate', handleCandidate);
signaller.removeListener('message:negotiate', handleNegotiateRequest);
}
function handleCandidate(data) {
q.addIceCandidate(data);
}
function handleSdp(sdp, src) {
emit('sdp.remote', sdp);
// if the source is unknown or not a match, then don't process
if ((! src) || (src.id !== targetId)) {
return;
}
q.setRemoteDescription(sdp);
}
function handleConnectionClose() {
debug('captured pc close, iceConnectionState = ' + pc.iceConnectionState);
decouple();
}
function handleDisconnect() {
debug('captured pc disconnect, monitoring connection status');
// start the disconnect timer
disconnectTimer = setTimeout(function() {
debug('manually closing connection after disconnect timeout');
cleanup(pc);
}, disconnectTimeout);
mon.on('statechange', handleDisconnectAbort);
}
function handleDisconnectAbort() {
debug('connection state changed to: ' + pc.iceConnectionState);
// if the state is checking, then do not reset the disconnect timer as
// we are doing our own checking
if (CHECKING_STATES.indexOf(pc.iceConnectionState) >= 0) {
return;
}
resetDisconnectTimer();
// if we have a closed or failed status, then close the connection
if (CLOSED_STATES.indexOf(pc.iceConnectionState) >= 0) {
return mon('closed');
}
mon.once('disconnect', handleDisconnect);
}
function handleLocalCandidate(evt) {
var data = evt.candidate && pluckCandidate(evt.candidate);
if (evt.candidate) {
resetDisconnectTimer();
emit('ice.local', data);
signaller.to(targetId).send('/candidate', data);
endOfCandidates = false;
}
else if (! endOfCandidates) {
endOfCandidates = true;
emit('ice.gathercomplete');
signaller.to(targetId).send('/endofcandidates', {});
}
}
function handleNegotiateRequest(src) {
if (src.id === targetId) {
emit('negotiate.request', src.id);
debounceOffer();
}
}
function resetDisconnectTimer() {
mon.off('statechange', handleDisconnectAbort);
// clear the disconnect timer
debug('reset disconnect timer, state: ' + pc.iceConnectionState);
clearTimeout(disconnectTimer);
}
// when regotiation is needed look for the peer
if (reactive) {
pc.onnegotiationneeded = function() {
emit('negotiate.renegotiate');
createOrRequestOffer();
};
}
pc.onicecandidate = handleLocalCandidate;
// when the task queue tells us we have sdp available, send that over the wire
q.on('sdp.local', function(desc) {
signaller.to(targetId).send('/sdp', desc);
});
// when we receive sdp, then
signaller.on('sdp', handleSdp);
signaller.on('candidate', handleCandidate);
// listeners (signaller >= 5)
signaller.on('message:sdp', handleSdp);
signaller.on('message:candidate', handleCandidate);
// if this is a master connection, listen for negotiate events
if (isMaster) {
signaller.on('negotiate', handleNegotiateRequest);
signaller.on('message:negotiate', handleNegotiateRequest); // signaller >= 5
}
// when the connection closes, remove event handlers
mon.once('closed', handleConnectionClose);
mon.once('disconnected', handleDisconnect);
// patch in the create offer functions
mon.createOffer = createOrRequestOffer;
return mon;
}
module.exports = couple;