| 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 |