Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / test / data / webrtc / message_handling.js
blob377f95f1aa63298f77b5e8e95334f55ba27f5a14
1 /**
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
4 * found in the LICENSE file.
5 */
7 // This file requires these functions to be defined globally by someone else:
8 // function handleMessage(peerConnection, message)
9 // function createPeerConnection(stun_server, useRtpDataChannel)
10 // function setupCall(peerConnection)
11 // function answerCall(peerConnection, message)
13 // Currently these functions are supplied by jsep01_call.js.
15 /**
16 * This object represents the call.
17 * @private
19 var gPeerConnection = null;
21 /**
22 * True if we are accepting incoming calls.
23 * @private
25 var gAcceptsIncomingCalls = true;
27 /**
28 * Our peer id as assigned by the peerconnection_server.
29 * @private
31 var gOurPeerId = null;
33 /**
34 * The client id we use to identify to peerconnection_server.
35 * @private
37 var gOurClientName = null;
39 /**
40 * The URL to the peerconnection_server.
41 * @private
43 var gServerUrl = null;
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
50 var gRemotePeerId = null;
52 /**
53 * Whether or not to auto-respond by adding our local stream when we are called.
54 * @private
56 var gAutoAddLocalToPeerConnectionStreamWhenCalled = true;
58 /**
59 * The one and only data channel.
60 * @private
62 var gDataChannel = null;
64 /**
65 * The DTMF sender.
66 * @private
68 var gDtmfSender = null;
70 /**
71 * We need a STUN server for some API calls.
72 * @private
74 var STUN_SERVER = 'stun.l.google.com:19302';
76 /**
77 * If true, any created peer connection will use RTP data
78 * channels. Otherwise it will use SCTP data channels.
80 var gUseRtpDataChannels = true;
82 // Public interface to tests.
85 /**
86 * Connects to the provided peerconnection_server.
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.
92 function connect(serverUrl, clientName) {
93 if (gOurPeerId != null)
94 throw failTest('connecting, but is already connected.');
96 debug('Connecting to ' + serverUrl + ' as ' + clientName);
97 gServerUrl = serverUrl;
98 gOurClientName = clientName;
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);
106 request.send();
110 * Checks if the remote peer has connected. Returns peer-connected if that is
111 * the case, otherwise no-peer-connected.
113 function remotePeerIsConnected() {
114 if (gRemotePeerId == null)
115 returnToTest('no-peer-connected');
116 else
117 returnToTest('peer-connected');
121 * Set if RTP data channels should be used for peerconnections.
122 * @param{boolean} useRtpDataChannel
124 function useRtpDataChannelsForNewPeerConnections(useRtpDataChannels) {
125 gUseRtpDataChannels = useRtpDataChannels;
129 * Creates a peer connection. Must be called before most other public functions
130 * in this file.
132 function preparePeerConnection() {
133 if (gPeerConnection != null)
134 throw failTest('creating peer connection, but we already have one.');
136 gPeerConnection = createPeerConnection(STUN_SERVER, gUseRtpDataChannels);
137 returnToTest('ok-peerconnection-created');
141 * Negotiates a call with the other side. This will create a peer connection on
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.
145 * To call this method we need to be aware of the other side, e.g. we must be
146 * connected to peerconnection_server and we must have exactly one peer on that
147 * server.
149 * This method may be called any number of times. If you haven't added any
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.
154 function negotiateCall() {
155 if (gPeerConnection == null)
156 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.');
162 setupCall(gPeerConnection);
163 returnToTest('ok-negotiating');
167 * 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.
170 function addLocalStream() {
171 if (gPeerConnection == null)
172 throw failTest('adding local stream, but we have no peer connection.');
174 addLocalStreamToPeerConnection(gPeerConnection);
175 returnToTest('ok-added');
179 * Loads a file with WebAudio and connects it to the peer connection.
181 * The loadAudioAndAddToPeerConnection will return ok-added to the test when
182 * the sound is loaded and added to the peer connection. The sound will start
183 * playing when you call playAudioFile.
185 * @param url URL pointing to the file to play. You can assume that you can
186 * serve files from the repository's file system. For instance, to serve a
187 * file from chrome/test/data/pyauto_private/webrtc/file.wav, pass in a path
188 * relative to this directory (e.g. ../pyauto_private/webrtc/file.wav).
190 function addAudioFile(url) {
191 if (gPeerConnection == null)
192 throw failTest('adding audio file, but we have no peer connection.');
194 loadAudioAndAddToPeerConnection(url, gPeerConnection);
198 * Mixes the local audio stream with an audio file through WebAudio.
200 * You must have successfully requested access to the user's microphone through
201 * getUserMedia before calling this function (see getUserMedia.js).
202 * Additionally, you must have loaded an audio file to mix with.
204 * When playAudioFile is called, WebAudio will effectively mix the user's
205 * microphone input with the previously loaded file and feed that into the
206 * peer connection.
208 function mixLocalStreamWithPreviouslyLoadedAudioFile() {
209 if (gPeerConnection == null)
210 throw failTest('trying to mix in stream, but we have no peer connection.');
211 if (getLocalStream() == null)
212 throw failTest('trying to mix in stream, but we have no stream to mix in.');
214 mixLocalStreamIntoPeerConnection(gPeerConnection, getLocalStream());
218 * Must be called after addAudioFile.
220 function playAudioFile() {
221 if (gPeerConnection == null)
222 throw failTest('trying to play file, but we have no peer connection.');
224 playPreviouslyLoadedAudioFile(gPeerConnection);
225 returnToTest('ok-playing');
229 * Removes the local stream from the peer connection. You will have to
230 * re-negotiate the call for this to take effect in the call.
232 function removeLocalStream() {
233 if (gPeerConnection == null)
234 throw failTest('attempting to remove local stream, but no call is up');
236 removeLocalStreamFromPeerConnection(gPeerConnection);
237 returnToTest('ok-local-stream-removed');
241 * (see getReadyState)
243 function getPeerConnectionReadyState() {
244 returnToTest(getReadyState());
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.
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.
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.');
265 var track = selectAudioOrVideoTrack(gPeerConnection.getRemoteStreams()[0]);
266 toggle_(track, 'remote', typeToToggle);
270 * See documentation on toggleRemoteStream (this function is the same except
271 * we are looking at local streams).
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.');
281 var track = selectAudioOrVideoTrack(gPeerConnection.getLocalStreams()[0]);
282 toggle_(track, 'local', typeToToggle);
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.
289 function hangUp() {
290 if (gPeerConnection == null)
291 throw failTest('hanging up, but has no peer connection');
292 if (getReadyState() != 'active')
293 throw failTest('hanging up, but ready state is not active (no call up).');
294 sendToPeer(gRemotePeerId, 'BYE');
295 closeCall_();
296 gAcceptsIncomingCalls = false;
297 returnToTest('ok-call-hung-up');
301 * Start accepting incoming calls again after a hangup.
303 function acceptIncomingCallsAgain() {
304 gAcceptsIncomingCalls = true;
308 * Do not auto-add the local stream when called.
310 function doNotAutoAddLocalStreamWhenCalled() {
311 gAutoAddLocalToPeerConnectionStreamWhenCalled = false;
315 * Disconnects from the peerconnection server. Returns ok-disconnected on
316 * success.
318 function disconnect() {
319 if (gOurPeerId == null)
320 throw failTest('Disconnecting, but we are not connected.');
322 request = new XMLHttpRequest();
323 request.open('GET', gServerUrl + '/sign_out?peer_id=' + gOurPeerId, false);
324 request.send();
325 gOurPeerId = null;
326 returnToTest('ok-disconnected');
330 * Creates a DataChannel on the current PeerConnection. Only one DataChannel can
331 * be created on each PeerConnection.
332 * Returns ok-datachannel-created on success.
334 function createDataChannelOnPeerConnection() {
335 if (gPeerConnection == null)
336 throw failTest('Tried to create data channel, ' +
337 'but have no peer connection.');
339 createDataChannel(gPeerConnection, gOurClientName);
340 returnToTest('ok-datachannel-created');
344 * Close the DataChannel on the current PeerConnection.
345 * Returns ok-datachannel-close on success.
347 function closeDataChannelOnPeerConnection() {
348 if (gPeerConnection == null)
349 throw failTest('Tried to close data channel, ' +
350 'but have no peer connection.');
352 closeDataChannel(gPeerConnection);
353 returnToTest('ok-datachannel-close');
357 * Creates a DTMF sender on the current PeerConnection.
358 * Returns ok-dtmfsender-created on success.
360 function createDtmfSenderOnPeerConnection() {
361 if (gPeerConnection == null)
362 throw failTest('Tried to create DTMF sender, ' +
363 'but have no peer connection.');
365 createDtmfSender(gPeerConnection);
366 returnToTest('ok-dtmfsender-created');
370 * Send DTMF tones on the gDtmfSender.
371 * Returns ok-dtmf-sent on success.
373 function insertDtmfOnSender(tones, duration, interToneGap) {
374 if (gDtmfSender == null)
375 throw failTest('Tried to insert DTMF tones, ' +
376 'but have no DTMF sender.');
378 insertDtmf(tones, duration, interToneGap);
379 returnToTest('ok-dtmf-sent');
382 // Public interface to signaling implementations, such as JSEP.
385 * Sends a message to a peer through the peerconnection_server.
387 function sendToPeer(peer, message) {
388 var messageToLog = message.sdp ? message.sdp : message;
389 debug('Sending message ' + messageToLog + ' to peer ' + peer + '.');
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);
399 * Returns true if we are disconnected from peerconnection_server.
401 function isDisconnected() {
402 return gOurPeerId == null;
406 * @return {!string} The current peer connection's ready state, or
407 * 'no-peer-connection' if there is no peer connection up.
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'.
413 function getReadyState() {
414 if (gPeerConnection == null)
415 return 'no-peer-connection';
417 return 'active';
420 // Internals.
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.');
428 track.enabled = !track.enabled;
429 returnToTest('ok-' + audioOrVideo + '-toggled-to-' + track.enabled);
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');
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');
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]);
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;
461 var remotePeerId = null;
462 for (var i = 0; i < peerList.length; i++) {
463 if (peerList[i].length == 0)
464 continue;
466 var parsed = peerList[i].split(',');
467 var name = parsed[0];
468 var id = parsed[1];
470 if (id != gOurPeerId) {
471 debug('Found remote peer with name ' + name + ', id ' +
472 id + ' when connecting.');
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.');
479 // Found a remote peer.
480 remotePeerId = id;
483 return remotePeerId;
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);
494 hangingGetRequest.ontimeout = function() {
495 hangingGetTimeoutCallback_(hangingGetRequest, server, ourId);
497 callUrl = server + '/wait?peer_id=' + ourId;
498 debug('Sending ' + callUrl);
499 hangingGetRequest.open('GET', callUrl, true);
500 hangingGetRequest.send();
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;
509 if (hangingGetRequest.status != 200) {
510 throw failTest('Error ' + hangingGetRequest.status + ' from server: ' +
511 hangingGetRequest.statusText);
513 var targetId = readResponseHeader_(hangingGetRequest, 'Pragma');
514 if (targetId == ourId)
515 handleServerNotification_(hangingGetRequest.responseText);
516 else
517 handlePeerMessage_(targetId, hangingGetRequest.responseText);
519 hangingGetRequest.abort();
520 restartHangingGet_(server, ourId);
523 /** @private */
524 function hangingGetTimeoutCallback_(hangingGetRequest, server, ourId) {
525 debug('Hanging GET times out, re-issuing...');
526 hangingGetRequest.abort();
527 restartHangingGet_(server, ourId);
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]);
543 /** @private */
544 function closeCall_() {
545 if (gPeerConnection == null)
546 throw failTest('Closing call, but no call active.');
547 gPeerConnection.close();
548 gPeerConnection = null;
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 + '.');
558 if (message.search('BYE') == 0) {
559 debug('Received BYE from peer: closing call');
560 closeCall_();
561 return;
563 if (gPeerConnection == null && gAcceptsIncomingCalls) {
564 // The other side is calling us.
565 debug('We are being called: answer...');
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);
573 answerCall(gPeerConnection, message);
574 return;
576 if (gPeerConnection == null) {
577 debug('Discarding message ' + message + '; already disconnected.');
578 return;
581 handleMessage(gPeerConnection, message);
584 /** @private */
585 function restartHangingGet_(server, ourId) {
586 window.setTimeout(function() {
587 startHangingGet_(server, ourId);
588 }, 0);
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 + '.');
598 return parseInt(value);