OLD | NEW |
1 /** | 1 /** |
2 * Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 * Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 * Use of this source code is governed by a BSD-style license that can be | 3 * Use of this source code is governed by a BSD-style license that can be |
4 * found in the LICENSE file. | 4 * found in the LICENSE file. |
5 */ | 5 */ |
6 | 6 |
7 // This file requires these functions to be defined globally by someone else: | 7 // This file requires these functions to be defined globally by someone else: |
8 // function handleMessage(peerConnection, message) | |
9 // function createPeerConnection(stun_server, useRtpDataChannel) | 8 // function createPeerConnection(stun_server, useRtpDataChannel) |
10 // function setupCall(peerConnection) | 9 // function createOffer(peerConnection, constraints, callback) |
11 // function answerCall(peerConnection, message) | 10 // function receiveOffer(peerConnection, offer, constraints, callback) |
| 11 // function receiveAnswer(peerConnection, answer, callback) |
12 | 12 |
13 // Currently these functions are supplied by jsep01_call.js. | 13 // Currently these functions are supplied by jsep01_call.js. |
14 | 14 |
15 /** | 15 /** |
| 16 * We need a STUN server for some API calls. |
| 17 * @private |
| 18 */ |
| 19 var STUN_SERVER = 'stun.l.google.com:19302'; |
| 20 |
| 21 /** |
16 * This object represents the call. | 22 * This object represents the call. |
17 * @private | 23 * @private |
18 */ | 24 */ |
19 var gPeerConnection = null; | 25 var gPeerConnection = null; |
20 | 26 |
21 /** | 27 /** |
22 * True if we are accepting incoming calls. | |
23 * @private | |
24 */ | |
25 var gAcceptsIncomingCalls = true; | |
26 | |
27 /** | |
28 * Our peer id as assigned by the peerconnection_server. | |
29 * @private | |
30 */ | |
31 var gOurPeerId = null; | |
32 | |
33 /** | |
34 * The client id we use to identify to peerconnection_server. | |
35 * @private | |
36 */ | |
37 var gOurClientName = null; | |
38 | |
39 /** | |
40 * The URL to the peerconnection_server. | |
41 * @private | |
42 */ | |
43 var gServerUrl = null; | |
44 | |
45 /** | |
46 * The remote peer's id. We receive this one either when we connect (in the case | |
47 * our peer connects before us) or in a notification later. | |
48 * @private | |
49 */ | |
50 var gRemotePeerId = null; | |
51 | |
52 /** | |
53 * Whether or not to auto-respond by adding our local stream when we are called. | |
54 * @private | |
55 */ | |
56 var gAutoAddLocalToPeerConnectionStreamWhenCalled = true; | |
57 | |
58 /** | |
59 * The one and only data channel. | |
60 * @private | |
61 */ | |
62 var gDataChannel = null; | |
63 | |
64 /** | |
65 * The DTMF sender. | |
66 * @private | |
67 */ | |
68 var gDtmfSender = null; | |
69 | |
70 /** | |
71 * We need a STUN server for some API calls. | |
72 * @private | |
73 */ | |
74 var STUN_SERVER = 'stun.l.google.com:19302'; | |
75 | |
76 /** | |
77 * If true, any created peer connection will use RTP data | 28 * If true, any created peer connection will use RTP data |
78 * channels. Otherwise it will use SCTP data channels. | 29 * channels. Otherwise it will use SCTP data channels. |
79 */ | 30 */ |
80 var gUseRtpDataChannels = true; | 31 var gUseRtpDataChannels = true; |
81 | 32 |
82 // Public interface to tests. | 33 /** |
| 34 * This stores ICE candidates generated on this side. |
| 35 * @private |
| 36 */ |
| 37 var gIceCandidates = []; |
83 | 38 |
84 | 39 // Public interface to tests. These are expected to be called with |
85 /** | 40 // ExecuteJavascript invocations from the browser tests and will return answers |
86 * Connects to the provided peerconnection_server. | 41 // through the DOM automation controller. |
87 * | |
88 * @param{string} serverUrl The server URL in string form without an ending | |
89 * slash, something like http://localhost:8888. | |
90 * @param{string} clientName The name to use when connecting to the server. | |
91 */ | |
92 function connect(serverUrl, clientName) { | |
93 if (gOurPeerId != null) | |
94 throw failTest('connecting, but is already connected.'); | |
95 | |
96 debug('Connecting to ' + serverUrl + ' as ' + clientName); | |
97 gServerUrl = serverUrl; | |
98 gOurClientName = clientName; | |
99 | |
100 request = new XMLHttpRequest(); | |
101 request.open('GET', serverUrl + '/sign_in?' + clientName, true); | |
102 debug(serverUrl + '/sign_in?' + clientName); | |
103 request.onreadystatechange = function() { | |
104 connectCallback_(request); | |
105 } | |
106 request.send(); | |
107 } | |
108 | |
109 /** | |
110 * Checks if the remote peer has connected. Returns peer-connected if that is | |
111 * the case, otherwise no-peer-connected. | |
112 */ | |
113 function remotePeerIsConnected() { | |
114 if (gRemotePeerId == null) | |
115 returnToTest('no-peer-connected'); | |
116 else | |
117 returnToTest('peer-connected'); | |
118 } | |
119 | |
120 /** | |
121 * Set if RTP data channels should be used for peerconnections. | |
122 * @param{boolean} useRtpDataChannel | |
123 */ | |
124 function useRtpDataChannelsForNewPeerConnections(useRtpDataChannels) { | |
125 gUseRtpDataChannels = useRtpDataChannels; | |
126 } | |
127 | 42 |
128 /** | 43 /** |
129 * Creates a peer connection. Must be called before most other public functions | 44 * Creates a peer connection. Must be called before most other public functions |
130 * in this file. | 45 * in this file. |
131 */ | 46 */ |
132 function preparePeerConnection() { | 47 function preparePeerConnection() { |
133 if (gPeerConnection != null) | 48 if (gPeerConnection != null) |
134 throw failTest('creating peer connection, but we already have one.'); | 49 throw failTest('creating peer connection, but we already have one.'); |
135 | 50 |
136 gPeerConnection = createPeerConnection(STUN_SERVER, gUseRtpDataChannels); | 51 gPeerConnection = createPeerConnection(STUN_SERVER, gUseRtpDataChannels); |
137 returnToTest('ok-peerconnection-created'); | 52 returnToTest('ok-peerconnection-created'); |
138 } | 53 } |
139 | 54 |
140 /** | 55 /** |
141 * Negotiates a call with the other side. This will create a peer connection on | 56 * Asks this page to create a local offer. |
142 * the other side if there isn't one. The other side will automatically add any | |
143 * stream it has unless doNotAutoAddLocalStreamWhenCalled() has been called. | |
144 * | 57 * |
145 * To call this method we need to be aware of the other side, e.g. we must be | 58 * Returns a string on the format ok-(JSON encoded session description). |
146 * connected to peerconnection_server and we must have exactly one peer on that | |
147 * server. | |
148 * | 59 * |
149 * This method may be called any number of times. If you haven't added any | 60 * @param {!object} constraints Any createOffer constraints. |
150 * streams to the call, an "empty" call will result. The method will return | |
151 * ok-negotiating immediately to the test if the negotiation was successfully | |
152 * sent. | |
153 */ | 61 */ |
154 function negotiateCall() { | 62 function createLocalOffer(constraints) { |
155 if (gPeerConnection == null) | 63 if (gPeerConnection == null) |
156 throw failTest('negotiating call, but we have no peer connection.'); | 64 throw failTest('Negotiating call, but we have no peer connection.'); |
157 if (gOurPeerId == null) | |
158 throw failTest('negotiating call, but not connected.'); | |
159 if (gRemotePeerId == null) | |
160 throw failTest('negotiating call, but missing remote peer.'); | |
161 | 65 |
162 setupCall(gPeerConnection); | 66 // TODO(phoglund): move jsep01.call stuff into this file and remove need |
163 returnToTest('ok-negotiating'); | 67 // of the createOffer method, etc. |
| 68 createOffer(gPeerConnection, constraints, function(localOffer) { |
| 69 returnToTest('ok-' + JSON.stringify(localOffer)); |
| 70 }); |
164 } | 71 } |
165 | 72 |
166 /** | 73 /** |
| 74 * Asks this page to accept an offer and generate an answer. |
| 75 * |
| 76 * Returns a string on the format ok-(JSON encoded session description). |
| 77 * |
| 78 * @param {!string} sessionDescJson A JSON-encoded session description of type |
| 79 * 'offer'. |
| 80 * @param {!object} constraints Any createAnswer constraints. |
| 81 */ |
| 82 function receiveOfferFromPeer(sessionDescJson, constraints) { |
| 83 if (gPeerConnection == null) |
| 84 throw failTest('Receiving offer, but we have no peer connection.'); |
| 85 |
| 86 offer = parseJson_(sessionDescJson); |
| 87 if (!offer.type) |
| 88 failTest('Got invalid session description from peer: ' + sessionDescJson); |
| 89 if (offer.type != 'offer') |
| 90 failTest('Expected to receive offer from peer, got ' + offer.type); |
| 91 |
| 92 receiveOffer(gPeerConnection, offer , constraints, function(answer) { |
| 93 returnToTest('ok-' + JSON.stringify(answer)); |
| 94 }); |
| 95 } |
| 96 |
| 97 /** |
| 98 * Asks this page to accept an answer generated by the peer in response to a |
| 99 * previous offer by this page |
| 100 * |
| 101 * Returns a string ok-accepted-answer on success. |
| 102 * |
| 103 * @param {!string} sessionDescJson A JSON-encoded session description of type |
| 104 * 'answer'. |
| 105 */ |
| 106 function receiveAnswerFromPeer(sessionDescJson) { |
| 107 if (gPeerConnection == null) |
| 108 throw failTest('Receiving offer, but we have no peer connection.'); |
| 109 |
| 110 answer = parseJson_(sessionDescJson); |
| 111 if (!answer.type) |
| 112 failTest('Got invalid session description from peer: ' + sessionDescJson); |
| 113 if (answer.type != 'answer') |
| 114 failTest('Expected to receive answer from peer, got ' + answer.type); |
| 115 |
| 116 receiveAnswer(gPeerConnection, answer, function() { |
| 117 returnToTest('ok-accepted-answer'); |
| 118 }); |
| 119 } |
| 120 |
| 121 /** |
167 * Adds the local stream to the peer connection. You will have to re-negotiate | 122 * Adds the local stream to the peer connection. You will have to re-negotiate |
168 * the call for this to take effect in the call. | 123 * the call for this to take effect in the call. |
169 */ | 124 */ |
170 function addLocalStream() { | 125 function addLocalStream() { |
171 if (gPeerConnection == null) | 126 if (gPeerConnection == null) |
172 throw failTest('adding local stream, but we have no peer connection.'); | 127 throw failTest('adding local stream, but we have no peer connection.'); |
173 | 128 |
174 addLocalStreamToPeerConnection(gPeerConnection); | 129 addLocalStreamToPeerConnection(gPeerConnection); |
175 returnToTest('ok-added'); | 130 returnToTest('ok-added'); |
176 } | 131 } |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
219 */ | 174 */ |
220 function playAudioFile() { | 175 function playAudioFile() { |
221 if (gPeerConnection == null) | 176 if (gPeerConnection == null) |
222 throw failTest('trying to play file, but we have no peer connection.'); | 177 throw failTest('trying to play file, but we have no peer connection.'); |
223 | 178 |
224 playPreviouslyLoadedAudioFile(gPeerConnection); | 179 playPreviouslyLoadedAudioFile(gPeerConnection); |
225 returnToTest('ok-playing'); | 180 returnToTest('ok-playing'); |
226 } | 181 } |
227 | 182 |
228 /** | 183 /** |
229 * Removes the local stream from the peer connection. You will have to | 184 * Hangs up a started call. Returns ok-call-hung-up on success. |
230 * re-negotiate the call for this to take effect in the call. | |
231 */ | |
232 function removeLocalStream() { | |
233 if (gPeerConnection == null) | |
234 throw failTest('attempting to remove local stream, but no call is up'); | |
235 | |
236 removeLocalStreamFromPeerConnection(gPeerConnection); | |
237 returnToTest('ok-local-stream-removed'); | |
238 } | |
239 | |
240 /** | |
241 * (see getReadyState) | |
242 */ | |
243 function getPeerConnectionReadyState() { | |
244 returnToTest(getReadyState()); | |
245 } | |
246 | |
247 /** | |
248 * Toggles the remote audio stream's enabled state on the peer connection, given | |
249 * that a call is active. Returns ok-[typeToToggle]-toggled-to-[true/false] | |
250 * on success. | |
251 * | |
252 * @param selectAudioOrVideoTrack: A function that takes a remote stream as | |
253 * argument and returns a track (e.g. either the video or audio track). | |
254 * @param typeToToggle: Either "audio" or "video" depending on what the selector | |
255 * function selects. | |
256 */ | |
257 function toggleRemoteStream(selectAudioOrVideoTrack, typeToToggle) { | |
258 if (gPeerConnection == null) | |
259 throw failTest('Tried to toggle remote stream, ' + | |
260 'but have no peer connection.'); | |
261 if (gPeerConnection.getRemoteStreams().length == 0) | |
262 throw failTest('Tried to toggle remote stream, ' + | |
263 'but not receiving any stream.'); | |
264 | |
265 var track = selectAudioOrVideoTrack(gPeerConnection.getRemoteStreams()[0]); | |
266 toggle_(track, 'remote', typeToToggle); | |
267 } | |
268 | |
269 /** | |
270 * See documentation on toggleRemoteStream (this function is the same except | |
271 * we are looking at local streams). | |
272 */ | |
273 function toggleLocalStream(selectAudioOrVideoTrack, typeToToggle) { | |
274 if (gPeerConnection == null) | |
275 throw failTest('Tried to toggle local stream, ' + | |
276 'but have no peer connection.'); | |
277 if (gPeerConnection.getLocalStreams().length == 0) | |
278 throw failTest('Tried to toggle local stream, but there is no local ' + | |
279 'stream in the call.'); | |
280 | |
281 var track = selectAudioOrVideoTrack(gPeerConnection.getLocalStreams()[0]); | |
282 toggle_(track, 'local', typeToToggle); | |
283 } | |
284 | |
285 /** | |
286 * Hangs up a started call. Returns ok-call-hung-up on success. This tab will | |
287 * not accept any incoming calls after this call. | |
288 */ | 185 */ |
289 function hangUp() { | 186 function hangUp() { |
290 if (gPeerConnection == null) | 187 if (gPeerConnection == null) |
291 throw failTest('hanging up, but has no peer connection'); | 188 throw failTest('hanging up, but has no peer connection'); |
292 if (getReadyState() != 'active') | 189 gPeerConnection.close(); |
293 throw failTest('hanging up, but ready state is not active (no call up).'); | 190 gPeerConnection = null; |
294 sendToPeer(gRemotePeerId, 'BYE'); | |
295 closeCall_(); | |
296 gAcceptsIncomingCalls = false; | |
297 returnToTest('ok-call-hung-up'); | 191 returnToTest('ok-call-hung-up'); |
298 } | 192 } |
299 | 193 |
300 /** | 194 /** |
301 * Start accepting incoming calls again after a hangup. | 195 * Retrieves all ICE candidates generated on this side. Must be called after |
| 196 * ICE candidate generation is triggered (for instance by running a call |
| 197 * negotiation). This function will wait if necessary if we're not done |
| 198 * generating ICE candidates on this side. |
| 199 * |
| 200 * Returns a JSON-encoded array of RTCIceCandidate instances to the test. |
302 */ | 201 */ |
303 function acceptIncomingCallsAgain() { | 202 function getAllIceCandidates() { |
304 gAcceptsIncomingCalls = true; | 203 if (gPeerConnection == null) |
| 204 throw failTest('Trying to get ICE candidates, but has no peer connection.'); |
| 205 |
| 206 if (gPeerConnection.iceGatheringState != 'complete') { |
| 207 console.log('Still ICE gathering - waiting...'); |
| 208 setTimeout(getAllIceCandidates, 100); |
| 209 return; |
| 210 } |
| 211 |
| 212 returnToTest(JSON.stringify(gIceCandidates)); |
305 } | 213 } |
306 | 214 |
307 /** | 215 /** |
308 * Do not auto-add the local stream when called. | 216 * Receives ICE candidates from the peer. |
| 217 * |
| 218 * Returns ok-received-candidates to the test on success. |
| 219 * |
| 220 * @param iceCandidatesJson a JSON-encoded array of RTCIceCandidate instances. |
309 */ | 221 */ |
310 function doNotAutoAddLocalStreamWhenCalled() { | 222 function receiveIceCandidates(iceCandidatesJson) { |
311 gAutoAddLocalToPeerConnectionStreamWhenCalled = false; | 223 if (gPeerConnection == null) |
312 } | 224 throw failTest('Received ICE candidate, but has no peer connection'); |
313 | 225 |
314 /** | 226 var iceCandidates = parseJson_(iceCandidatesJson); |
315 * Disconnects from the peerconnection server. Returns ok-disconnected on | 227 if (!iceCandidates.length) |
316 * success. | 228 throw failTest('Received invalid ICE candidate list from peer: ' + |
317 */ | 229 iceCandidatesJson); |
318 function disconnect() { | |
319 if (gOurPeerId == null) | |
320 throw failTest('Disconnecting, but we are not connected.'); | |
321 | 230 |
322 request = new XMLHttpRequest(); | 231 iceCandidates.forEach(function(iceCandidate) { |
323 request.open('GET', gServerUrl + '/sign_out?peer_id=' + gOurPeerId, false); | 232 if (!iceCandidate.candidate) |
324 request.send(); | 233 failTest('Received invalid ICE candidate from peer: ' + |
325 gOurPeerId = null; | 234 iceCandidatesJson); |
326 returnToTest('ok-disconnected'); | |
327 } | |
328 | 235 |
329 /** | 236 gPeerConnection.addIceCandidate(new RTCIceCandidate(iceCandidate)); |
330 * Creates a DataChannel on the current PeerConnection. Only one DataChannel can | 237 }); |
331 * be created on each PeerConnection. | |
332 * Returns ok-datachannel-created on success. | |
333 */ | |
334 function createDataChannelOnPeerConnection() { | |
335 if (gPeerConnection == null) | |
336 throw failTest('Tried to create data channel, ' + | |
337 'but have no peer connection.'); | |
338 | 238 |
339 createDataChannel(gPeerConnection, gOurClientName); | 239 returnToTest('ok-received-candidates'); |
340 returnToTest('ok-datachannel-created'); | |
341 } | |
342 | |
343 /** | |
344 * Close the DataChannel on the current PeerConnection. | |
345 * Returns ok-datachannel-close on success. | |
346 */ | |
347 function closeDataChannelOnPeerConnection() { | |
348 if (gPeerConnection == null) | |
349 throw failTest('Tried to close data channel, ' + | |
350 'but have no peer connection.'); | |
351 | |
352 closeDataChannel(gPeerConnection); | |
353 returnToTest('ok-datachannel-close'); | |
354 } | |
355 | |
356 /** | |
357 * Creates a DTMF sender on the current PeerConnection. | |
358 * Returns ok-dtmfsender-created on success. | |
359 */ | |
360 function createDtmfSenderOnPeerConnection() { | |
361 if (gPeerConnection == null) | |
362 throw failTest('Tried to create DTMF sender, ' + | |
363 'but have no peer connection.'); | |
364 | |
365 createDtmfSender(gPeerConnection); | |
366 returnToTest('ok-dtmfsender-created'); | |
367 } | |
368 | |
369 /** | |
370 * Send DTMF tones on the gDtmfSender. | |
371 * Returns ok-dtmf-sent on success. | |
372 */ | |
373 function insertDtmfOnSender(tones, duration, interToneGap) { | |
374 if (gDtmfSender == null) | |
375 throw failTest('Tried to insert DTMF tones, ' + | |
376 'but have no DTMF sender.'); | |
377 | |
378 insertDtmf(tones, duration, interToneGap); | |
379 returnToTest('ok-dtmf-sent'); | |
380 } | 240 } |
381 | 241 |
382 // Public interface to signaling implementations, such as JSEP. | 242 // Public interface to signaling implementations, such as JSEP. |
383 | 243 |
384 /** | 244 /** |
385 * Sends a message to a peer through the peerconnection_server. | 245 * Enqueues an ICE candidate for sending to the peer. |
| 246 * |
| 247 * @param {!RTCIceCandidate} The ICE candidate to send. |
386 */ | 248 */ |
387 function sendToPeer(peer, message) { | 249 function sendIceCandidate(message) { |
388 var messageToLog = message.sdp ? message.sdp : message; | 250 gIceCandidates.push(message); |
389 debug('Sending message ' + messageToLog + ' to peer ' + peer + '.'); | |
390 | |
391 var request = new XMLHttpRequest(); | |
392 var url = gServerUrl + '/message?peer_id=' + gOurPeerId + '&to=' + peer; | |
393 request.open('POST', url, false); | |
394 request.setRequestHeader('Content-Type', 'text/plain'); | |
395 request.send(message); | |
396 } | 251 } |
397 | 252 |
398 /** | 253 /** |
399 * Returns true if we are disconnected from peerconnection_server. | 254 * Parses JSON-encoded session descriptions and ICE candidates. |
| 255 * @private |
400 */ | 256 */ |
401 function isDisconnected() { | 257 function parseJson_(json) { |
402 return gOurPeerId == null; | 258 // Escape since the \r\n in the SDP tend to get unescaped. |
403 } | 259 jsonWithEscapedLineBreaks = json.replace(/\r\n/g, '\\r\\n'); |
404 | 260 try { |
405 /** | 261 return JSON.parse(jsonWithEscapedLineBreaks); |
406 * @return {!string} The current peer connection's ready state, or | 262 } catch (exception) { |
407 * 'no-peer-connection' if there is no peer connection up. | 263 failTest('Failed to parse JSON: ' + jsonWithEscapedLineBreaks + ', got ' + |
408 * | 264 exception); |
409 * NOTE: The PeerConnection states are changing and until chromium has | |
410 * implemented the new states we have to use this interim solution of | |
411 * always assuming that the PeerConnection is 'active'. | |
412 */ | |
413 function getReadyState() { | |
414 if (gPeerConnection == null) | |
415 return 'no-peer-connection'; | |
416 | |
417 return 'active'; | |
418 } | |
419 | |
420 // Internals. | |
421 | |
422 /** @private */ | |
423 function toggle_(track, localOrRemote, audioOrVideo) { | |
424 if (!track) | |
425 throw failTest('Tried to toggle ' + localOrRemote + ' ' + audioOrVideo + | |
426 ' stream, but has no such stream.'); | |
427 | |
428 track.enabled = !track.enabled; | |
429 returnToTest('ok-' + audioOrVideo + '-toggled-to-' + track.enabled); | |
430 } | |
431 | |
432 /** @private */ | |
433 function connectCallback_(request) { | |
434 debug('Connect callback: ' + request.status + ', ' + request.readyState); | |
435 if (request.status == 0) { | |
436 debug('peerconnection_server doesn\'t seem to be up.'); | |
437 returnToTest('failed-to-connect'); | |
438 } | |
439 if (request.readyState == 4 && request.status == 200) { | |
440 gOurPeerId = parseOurPeerId_(request.responseText); | |
441 gRemotePeerId = parseRemotePeerIdIfConnected_(request.responseText); | |
442 startHangingGet_(gServerUrl, gOurPeerId); | |
443 returnToTest('ok-connected'); | |
444 } | 265 } |
445 } | 266 } |
446 | |
447 /** @private */ | |
448 function parseOurPeerId_(responseText) { | |
449 // According to peerconnection_server's protocol. | |
450 var peerList = responseText.split('\n'); | |
451 return parseInt(peerList[0].split(',')[1]); | |
452 } | |
453 | |
454 /** @private */ | |
455 function parseRemotePeerIdIfConnected_(responseText) { | |
456 var peerList = responseText.split('\n'); | |
457 if (peerList.length == 1) { | |
458 // No peers have connected yet - we'll get their id later in a notification. | |
459 return null; | |
460 } | |
461 var remotePeerId = null; | |
462 for (var i = 0; i < peerList.length; i++) { | |
463 if (peerList[i].length == 0) | |
464 continue; | |
465 | |
466 var parsed = peerList[i].split(','); | |
467 var name = parsed[0]; | |
468 var id = parsed[1]; | |
469 | |
470 if (id != gOurPeerId) { | |
471 debug('Found remote peer with name ' + name + ', id ' + | |
472 id + ' when connecting.'); | |
473 | |
474 // There should be at most one remote peer in this test. | |
475 if (remotePeerId != null) | |
476 throw failTest('Expected just one remote peer in this test: ' + | |
477 'found several.'); | |
478 | |
479 // Found a remote peer. | |
480 remotePeerId = id; | |
481 } | |
482 } | |
483 return remotePeerId; | |
484 } | |
485 | |
486 /** @private */ | |
487 function startHangingGet_(server, ourId) { | |
488 if (isDisconnected()) | |
489 return; | |
490 hangingGetRequest = new XMLHttpRequest(); | |
491 hangingGetRequest.onreadystatechange = function() { | |
492 hangingGetCallback_(hangingGetRequest, server, ourId); | |
493 } | |
494 hangingGetRequest.ontimeout = function() { | |
495 hangingGetTimeoutCallback_(hangingGetRequest, server, ourId); | |
496 } | |
497 callUrl = server + '/wait?peer_id=' + ourId; | |
498 debug('Sending ' + callUrl); | |
499 hangingGetRequest.open('GET', callUrl, true); | |
500 hangingGetRequest.send(); | |
501 } | |
502 | |
503 /** @private */ | |
504 function hangingGetCallback_(hangingGetRequest, server, ourId) { | |
505 if (hangingGetRequest.readyState != 4 || hangingGetRequest.status == 0) { | |
506 // Code 0 is not possible if the server actually responded. Ignore. | |
507 return; | |
508 } | |
509 if (hangingGetRequest.status != 200) { | |
510 throw failTest('Error ' + hangingGetRequest.status + ' from server: ' + | |
511 hangingGetRequest.statusText); | |
512 } | |
513 var targetId = readResponseHeader_(hangingGetRequest, 'Pragma'); | |
514 if (targetId == ourId) | |
515 handleServerNotification_(hangingGetRequest.responseText); | |
516 else | |
517 handlePeerMessage_(targetId, hangingGetRequest.responseText); | |
518 | |
519 hangingGetRequest.abort(); | |
520 restartHangingGet_(server, ourId); | |
521 } | |
522 | |
523 /** @private */ | |
524 function hangingGetTimeoutCallback_(hangingGetRequest, server, ourId) { | |
525 debug('Hanging GET times out, re-issuing...'); | |
526 hangingGetRequest.abort(); | |
527 restartHangingGet_(server, ourId); | |
528 } | |
529 | |
530 /** @private */ | |
531 function handleServerNotification_(message) { | |
532 var parsed = message.split(','); | |
533 if (parseInt(parsed[2]) == 1) { | |
534 // Peer connected - this must be our remote peer, and it must mean we | |
535 // connected before them (except if we happened to connect to the server | |
536 // at precisely the same moment). | |
537 debug('Found remote peer with name ' + parsed[0] + ', id ' + | |
538 parsed[1] + ' when connecting.'); | |
539 gRemotePeerId = parseInt(parsed[1]); | |
540 } | |
541 } | |
542 | |
543 /** @private */ | |
544 function closeCall_() { | |
545 if (gPeerConnection == null) | |
546 throw failTest('Closing call, but no call active.'); | |
547 gPeerConnection.close(); | |
548 gPeerConnection = null; | |
549 } | |
550 | |
551 /** @private */ | |
552 function handlePeerMessage_(peerId, message) { | |
553 debug('Received message from peer ' + peerId + ': ' + message); | |
554 if (peerId != gRemotePeerId) { | |
555 throw failTest('Received notification from unknown peer ' + peerId + | |
556 ' (only know about ' + gRemotePeerId + '.'); | |
557 } | |
558 if (message.search('BYE') == 0) { | |
559 debug('Received BYE from peer: closing call'); | |
560 closeCall_(); | |
561 return; | |
562 } | |
563 if (gPeerConnection == null && gAcceptsIncomingCalls) { | |
564 // The other side is calling us. | |
565 debug('We are being called: answer...'); | |
566 | |
567 gPeerConnection = createPeerConnection(STUN_SERVER, gUseRtpDataChannels); | |
568 if (gAutoAddLocalToPeerConnectionStreamWhenCalled && | |
569 obtainGetUserMediaResult() == 'ok-got-stream') { | |
570 debug('We have a local stream, so hook it up automatically.'); | |
571 addLocalStreamToPeerConnection(gPeerConnection); | |
572 } | |
573 answerCall(gPeerConnection, message); | |
574 return; | |
575 } | |
576 if (gPeerConnection == null) { | |
577 debug('Discarding message ' + message + '; already disconnected.'); | |
578 return; | |
579 } | |
580 | |
581 handleMessage(gPeerConnection, message); | |
582 } | |
583 | |
584 /** @private */ | |
585 function restartHangingGet_(server, ourId) { | |
586 window.setTimeout(function() { | |
587 startHangingGet_(server, ourId); | |
588 }, 0); | |
589 } | |
590 | |
591 /** @private */ | |
592 function readResponseHeader_(request, key) { | |
593 var value = request.getResponseHeader(key) | |
594 if (value == null || value.length == 0) { | |
595 throw failTest('Received empty value ' + value + | |
596 ' for response header key ' + key + '.'); | |
597 } | |
598 return parseInt(value); | |
599 } | |
OLD | NEW |