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 * Must be called after addAudioFile.
152 function playAudioFile() {
153 playPreviouslyLoadedAudioFile(peerConnection_());
154 returnToTest('ok-playing');
158 * Hangs up a started call. Returns ok-call-hung-up on success.
161 peerConnection_().close();
162 gPeerConnection
= null;
163 returnToTest('ok-call-hung-up');
167 * Retrieves all ICE candidates generated on this side. Must be called after
168 * ICE candidate generation is triggered (for instance by running a call
169 * negotiation). This function will wait if necessary if we're not done
170 * generating ICE candidates on this side.
172 * Returns a JSON-encoded array of RTCIceCandidate instances to the test.
174 function getAllIceCandidates() {
175 if (peerConnection_().iceGatheringState
!= 'complete') {
176 console
.log('Still ICE gathering - waiting...');
177 setTimeout(getAllIceCandidates
, 100);
181 returnToTest(JSON
.stringify(gIceCandidates
));
185 * Receives ICE candidates from the peer.
187 * Returns ok-received-candidates to the test on success.
189 * @param iceCandidatesJson a JSON-encoded array of RTCIceCandidate instances.
191 function receiveIceCandidates(iceCandidatesJson
) {
192 var iceCandidates
= parseJson_(iceCandidatesJson
);
193 if (!iceCandidates
.length
)
194 throw failTest('Received invalid ICE candidate list from peer: ' +
197 iceCandidates
.forEach(function(iceCandidate
) {
198 if (!iceCandidate
.candidate
)
199 failTest('Received invalid ICE candidate from peer: ' +
202 peerConnection_().addIceCandidate(new RTCIceCandidate(iceCandidate
,
203 function() { success_('addIceCandidate'); },
204 function(error
) { failure_('addIceCandidate', error
); }
208 returnToTest('ok-received-candidates');
212 * Sets the mute state of the selected media element.
214 * Returns ok-muted on success.
216 * @param elementId The id of the element to mute.
217 * @param muted The mute state to set.
219 function setMediaElementMuted(elementId
, muted
) {
220 var element
= document
.getElementById(elementId
);
222 throw failTest('Cannot mute ' + elementId
+ '; does not exist.');
223 element
.muted
= muted
;
224 returnToTest('ok-muted');
230 function hasSeenCryptoInSdp() {
231 returnToTest(gHasSeenCryptoInSdp
);
237 function createPeerConnection_(stun_server
) {
238 servers
= {iceServers
: [{url
: 'stun:' + stun_server
}]};
240 peerConnection
= new RTCPeerConnection(servers
, {});
241 } catch (exception
) {
242 throw failTest('Failed to create peer connection: ' + exception
);
244 peerConnection
.onaddstream
= addStreamCallback_
;
245 peerConnection
.onremovestream
= removeStreamCallback_
;
246 peerConnection
.onicecandidate
= iceCallback_
;
247 return peerConnection
;
251 function peerConnection_() {
252 if (gPeerConnection
== null)
253 throw failTest('Trying to use peer connection, but none was created.');
254 return gPeerConnection
;
258 function success_(method
) {
259 debug(method
+ '(): success.');
263 function failure_(method
, error
) {
264 throw failTest(method
+ '() failed: ' + JSON
.stringify(error
));
268 function iceCallback_(event
) {
270 gIceCandidates
.push(event
.candidate
);
274 function setLocalDescription(peerConnection
, sessionDescription
) {
275 if (sessionDescription
.sdp
.search('a=crypto') != -1 ||
276 sessionDescription
.sdp
.search('a=fingerprint') != -1)
277 gHasSeenCryptoInSdp
= 'crypto-seen';
279 peerConnection
.setLocalDescription(
281 function() { success_('setLocalDescription'); },
282 function(error
) { failure_('setLocalDescription', error
); });
286 function addStreamCallback_(event
) {
287 debug('Receiving remote stream...');
288 var videoTag
= document
.getElementById('remote-view');
289 attachMediaStream(videoTag
, event
.stream
);
293 function removeStreamCallback_(event
) {
294 debug('Call ended.');
295 document
.getElementById('remote-view').src
= '';
299 * Parses JSON-encoded session descriptions and ICE candidates.
302 function parseJson_(json
) {
303 // Escape since the \r\n in the SDP tend to get unescaped.
304 jsonWithEscapedLineBreaks
= json
.replace(/\r\n/g, '\\r\\n');
306 return JSON
.parse(jsonWithEscapedLineBreaks
);
307 } catch (exception
) {
308 failTest('Failed to parse JSON: ' + jsonWithEscapedLineBreaks
+ ', got ' +