2 * Copyright 2014 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.
8 * We need a STUN server for some API calls.
11 var STUN_SERVER = 'stun.l.google.com:19302';
14 * The one and only peer connection in this page.
17 var gPeerConnection = null;
20 * This stores ICE candidates generated on this side.
23 var gIceCandidates = [];
26 * Keeps track of whether we have seen crypto information in the SDP.
29 var gHasSeenCryptoInSdp = 'no-crypto-seen';
31 // Public interface to tests. These are expected to be called with
32 // ExecuteJavascript invocations from the browser tests and will return answers
33 // through the DOM automation controller.
36 * Creates a peer connection. Must be called before most other public functions
39 function preparePeerConnection() {
40 if (gPeerConnection != null)
41 throw failTest('creating peer connection, but we already have one.');
43 gPeerConnection = createPeerConnection_(STUN_SERVER);
44 returnToTest('ok-peerconnection-created');
48 * Asks this page to create a local offer.
50 * Returns a string on the format ok-(JSON encoded session description).
52 * @param {!Object} constraints Any createOffer constraints.
54 function createLocalOffer(constraints) {
55 peerConnection_().createOffer(
56 function(localOffer) {
57 success_('createOffer');
58 setLocalDescription(peerConnection, localOffer);
60 returnToTest('ok-' + JSON.stringify(localOffer));
62 function(error) { failure_('createOffer', error); },
67 * Asks this page to accept an offer and generate an answer.
69 * Returns a string on the format ok-(JSON encoded session description).
71 * @param {!string} sessionDescJson A JSON-encoded session description of type
73 * @param {!Object} constraints Any createAnswer constraints.
75 function receiveOfferFromPeer(sessionDescJson, constraints) {
76 offer = parseJson_(sessionDescJson);
78 failTest('Got invalid session description from peer: ' + sessionDescJson);
79 if (offer.type != 'offer')
80 failTest('Expected to receive offer from peer, got ' + offer.type);
82 var sessionDescription = new RTCSessionDescription(offer);
83 peerConnection_().setRemoteDescription(
85 function() { success_('setRemoteDescription'); },
86 function(error) { failure_('setRemoteDescription', error); });
88 peerConnection_().createAnswer(
90 success_('createAnswer');
91 setLocalDescription(peerConnection, answer);
92 returnToTest('ok-' + JSON.stringify(answer));
94 function(error) { failure_('createAnswer', error); },
99 * Asks this page to accept an answer generated by the peer in response to a
100 * previous offer by this page
102 * Returns a string ok-accepted-answer on success.
104 * @param {!string} sessionDescJson A JSON-encoded session description of type
107 function receiveAnswerFromPeer(sessionDescJson) {
108 answer = parseJson_(sessionDescJson);
110 failTest('Got invalid session description from peer: ' + sessionDescJson);
111 if (answer.type != 'answer')
112 failTest('Expected to receive answer from peer, got ' + answer.type);
114 var sessionDescription = new RTCSessionDescription(answer);
115 peerConnection_().setRemoteDescription(
118 success_('setRemoteDescription');
119 returnToTest('ok-accepted-answer');
121 function(error) { failure_('setRemoteDescription', error); });
125 * Adds the local stream to the peer connection. You will have to re-negotiate
126 * the call for this to take effect in the call.
128 function addLocalStream() {
129 addLocalStreamToPeerConnection(peerConnection_());
130 returnToTest('ok-added');
134 * Loads a file with WebAudio and connects it to the peer connection.
136 * The loadAudioAndAddToPeerConnection will return ok-added to the test when
137 * the sound is loaded and added to the peer connection. The sound will start
138 * playing when you call playAudioFile.
140 * @param url URL pointing to the file to play. You can assume that you can
141 * serve files from the repository's file system. For instance, to serve a
142 * file from chrome/test/data/pyauto_private/webrtc/file.wav, pass in a path
143 * relative to this directory (e.g. ../pyauto_private/webrtc/file.wav).
145 function addAudioFile(url) {
146 loadAudioAndAddToPeerConnection(url, peerConnection_());
150 * Mixes the local audio stream with an audio file through WebAudio.
152 * You must have successfully requested access to the user's microphone through
153 * getUserMedia before calling this function (see getUserMedia.js).
154 * Additionally, you must have loaded an audio file to mix with.
156 * When playAudioFile is called, WebAudio will effectively mix the user's
157 * microphone input with the previously loaded file and feed that into the
160 function mixLocalStreamWithPreviouslyLoadedAudioFile() {
161 if (getLocalStream() == null)
162 throw failTest('trying to mix in stream, but we have no stream to mix in.');
164 mixLocalStreamIntoPeerConnection(peerConnection_(), getLocalStream());
168 * Must be called after addAudioFile.
170 function playAudioFile() {
171 playPreviouslyLoadedAudioFile(peerConnection_());
172 returnToTest('ok-playing');
176 * Hangs up a started call. Returns ok-call-hung-up on success.
179 peerConnection_().close();
180 gPeerConnection = null;
181 returnToTest('ok-call-hung-up');
185 * Retrieves all ICE candidates generated on this side. Must be called after
186 * ICE candidate generation is triggered (for instance by running a call
187 * negotiation). This function will wait if necessary if we're not done
188 * generating ICE candidates on this side.
190 * Returns a JSON-encoded array of RTCIceCandidate instances to the test.
192 function getAllIceCandidates() {
193 if (peerConnection_().iceGatheringState != 'complete') {
194 console.log('Still ICE gathering - waiting...');
195 setTimeout(getAllIceCandidates, 100);
199 returnToTest(JSON.stringify(gIceCandidates));
203 * Receives ICE candidates from the peer.
205 * Returns ok-received-candidates to the test on success.
207 * @param iceCandidatesJson a JSON-encoded array of RTCIceCandidate instances.
209 function receiveIceCandidates(iceCandidatesJson) {
210 var iceCandidates = parseJson_(iceCandidatesJson);
211 if (!iceCandidates.length)
212 throw failTest('Received invalid ICE candidate list from peer: ' +
215 iceCandidates.forEach(function(iceCandidate) {
216 if (!iceCandidate.candidate)
217 failTest('Received invalid ICE candidate from peer: ' +
220 peerConnection_().addIceCandidate(new RTCIceCandidate(iceCandidate,
221 function() { success_('addIceCandidate'); },
222 function(error) { failure_('addIceCandidate', error); }
226 returnToTest('ok-received-candidates');
232 function hasSeenCryptoInSdp() {
233 returnToTest(gHasSeenCryptoInSdp);
239 function createPeerConnection_(stun_server) {
240 servers = {iceServers: [{url: 'stun:' + stun_server}]};
242 peerConnection = new RTCPeerConnection(servers, {});
243 } catch (exception) {
244 throw failTest('Failed to create peer connection: ' + exception);
246 peerConnection.onaddstream = addStreamCallback_;
247 peerConnection.onremovestream = removeStreamCallback_;
248 peerConnection.onicecandidate = iceCallback_;
249 return peerConnection;
253 function peerConnection_() {
254 if (gPeerConnection == null)
255 throw failTest('Trying to use peer connection, but none was created.');
256 return gPeerConnection;
260 function success_(method) {
261 debug(method + '(): success.');
265 function failure_(method, error) {
266 throw failTest(method + '() failed: ' + JSON.stringify(error));
270 function iceCallback_(event) {
272 gIceCandidates.push(event.candidate);
276 function setLocalDescription(peerConnection, sessionDescription) {
277 if (sessionDescription.sdp.search('a=crypto') != -1 ||
278 sessionDescription.sdp.search('a=fingerprint') != -1)
279 gHasSeenCryptoInSdp = 'crypto-seen';
281 peerConnection.setLocalDescription(
283 function() { success_('setLocalDescription'); },
284 function(error) { failure_('setLocalDescription', error); });
288 function addStreamCallback_(event) {
289 debug('Receiving remote stream...');
290 var videoTag = document.getElementById('remote-view');
291 attachMediaStream(videoTag, event.stream);
295 function removeStreamCallback_(event) {
296 debug('Call ended.');
297 document.getElementById('remote-view').src = '';
301 * Parses JSON-encoded session descriptions and ICE candidates.
304 function parseJson_(json) {
305 // Escape since the \r\n in the SDP tend to get unescaped.
306 jsonWithEscapedLineBreaks = json.replace(/\r\n/g, '\\r\\n');
308 return JSON.parse(jsonWithEscapedLineBreaks);
309 } catch (exception) {
310 failTest('Failed to parse JSON: ' + jsonWithEscapedLineBreaks + ', got ' +