blob: adcb475b24d13c485e7ba406899e95ddc362f6eb [file] [log] [blame]
/**
* Copyright 2014 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/**
* The one and only peer connection in this page.
* @private
*/
var gPeerConnection = null;
/**
* This stores ICE candidates generated on this side.
* @private
*/
var gIceCandidates = [];
/**
* Keeps track of whether we have seen crypto information in the SDP.
* @private
*/
var gHasSeenCryptoInSdp = 'no-crypto-seen';
// Public interface to tests. These are expected to be called with
// ExecuteJavascript invocations from the browser tests and will return answers
// through the DOM automation controller.
/**
* Creates a peer connection. Must be called before most other public functions
* in this file.
*/
function preparePeerConnection() {
if (gPeerConnection != null)
throw failTest('creating peer connection, but we already have one.');
gPeerConnection = createPeerConnection_();
returnToTest('ok-peerconnection-created');
}
/**
* Asks this page to create a local offer.
*
* Returns a string on the format ok-(JSON encoded session description).
*
* @param {!Object} constraints Any createOffer constraints.
* @param {string} videoCodec If not null, promotes the specified codec to be
* the default video codec, e.g. the first one in the list on the 'm=video'
* SDP offer line. |videoCodec| is the case-sensitive codec name, e.g.
* 'VP8' or 'H264'.
*/
function createLocalOffer(constraints, videoCodec = null) {
peerConnection_().createOffer(
function(localOffer) {
success('createOffer');
setLocalDescription(peerConnection, localOffer);
if (videoCodec !== null)
localOffer.sdp = setSdpDefaultVideoCodec(localOffer.sdp, videoCodec);
returnToTest('ok-' + JSON.stringify(localOffer));
},
function(error) { failure('createOffer', error); },
constraints);
}
/**
* Asks this page to accept an offer and generate an answer.
*
* Returns a string on the format ok-(JSON encoded session description).
*
* @param {!string} sessionDescJson A JSON-encoded session description of type
* 'offer'.
* @param {!Object} constraints Any createAnswer constraints.
*/
function receiveOfferFromPeer(sessionDescJson, constraints) {
offer = parseJson_(sessionDescJson);
if (!offer.type)
failTest('Got invalid session description from peer: ' + sessionDescJson);
if (offer.type != 'offer')
failTest('Expected to receive offer from peer, got ' + offer.type);
var sessionDescription = new RTCSessionDescription(offer);
peerConnection_().setRemoteDescription(
sessionDescription,
function() { success('setRemoteDescription'); },
function(error) { failure('setRemoteDescription', error); });
peerConnection_().createAnswer(
function(answer) {
success('createAnswer');
setLocalDescription(peerConnection, answer);
returnToTest('ok-' + JSON.stringify(answer));
},
function(error) { failure('createAnswer', error); },
constraints);
}
/**
* Verifies that the specified codec is the default video codec, e.g. the first
* one in the list on the 'm=video' SDP answer line. If this is not the case,
* |failure| occurs.
*
* @param {!string} sessionDescJson A JSON-encoded session description.
* @param {!string} expectedVideoCodec The case-sensitive codec name, e.g.
* 'VP8' or 'H264'.
*/
function verifyDefaultVideoCodec(sessionDescJson, expectedVideoCodec) {
var sessionDesc = parseJson_(sessionDescJson);
if (!sessionDesc.type) {
failure('verifyDefaultVideoCodec',
'Invalid session description: ' + sessionDescJson);
}
var defaultVideoCodec = getSdpDefaultVideoCodec(sessionDesc.sdp);
if (defaultVideoCodec === null) {
failure('verifyDefaultVideoCodec',
'Could not determine default video codec.');
}
if (expectedVideoCodec !== defaultVideoCodec) {
failure('verifyDefaultVideoCodec',
'Expected default video codec ' + expectedVideoCodec +
', got ' + defaultVideoCodec + '.');
}
returnToTest('ok-verified');
}
/**
* Asks this page to accept an answer generated by the peer in response to a
* previous offer by this page
*
* Returns a string ok-accepted-answer on success.
*
* @param {!string} sessionDescJson A JSON-encoded session description of type
* 'answer'.
*/
function receiveAnswerFromPeer(sessionDescJson) {
answer = parseJson_(sessionDescJson);
if (!answer.type)
failTest('Got invalid session description from peer: ' + sessionDescJson);
if (answer.type != 'answer')
failTest('Expected to receive answer from peer, got ' + answer.type);
var sessionDescription = new RTCSessionDescription(answer);
peerConnection_().setRemoteDescription(
sessionDescription,
function() {
success('setRemoteDescription');
returnToTest('ok-accepted-answer');
},
function(error) { failure('setRemoteDescription', error); });
}
/**
* Adds the local stream to the peer connection. You will have to re-negotiate
* the call for this to take effect in the call.
*/
function addLocalStream() {
addLocalStreamToPeerConnection(peerConnection_());
returnToTest('ok-added');
}
/**
* Loads a file with WebAudio and connects it to the peer connection.
*
* The loadAudioAndAddToPeerConnection will return ok-added to the test when
* the sound is loaded and added to the peer connection. The sound will start
* playing when you call playAudioFile.
*
* @param url URL pointing to the file to play. You can assume that you can
* serve files from the repository's file system. For instance, to serve a
* file from chrome/test/data/pyauto_private/webrtc/file.wav, pass in a path
* relative to this directory (e.g. ../pyauto_private/webrtc/file.wav).
*/
function addAudioFile(url) {
loadAudioAndAddToPeerConnection(url, peerConnection_());
}
/**
* Must be called after addAudioFile.
*/
function playAudioFile() {
playPreviouslyLoadedAudioFile(peerConnection_());
returnToTest('ok-playing');
}
/**
* Hangs up a started call. Returns ok-call-hung-up on success.
*/
function hangUp() {
peerConnection_().close();
gPeerConnection = null;
returnToTest('ok-call-hung-up');
}
/**
* Retrieves all ICE candidates generated on this side. Must be called after
* ICE candidate generation is triggered (for instance by running a call
* negotiation). This function will wait if necessary if we're not done
* generating ICE candidates on this side.
*
* Returns a JSON-encoded array of RTCIceCandidate instances to the test.
*/
function getAllIceCandidates() {
if (peerConnection_().iceGatheringState != 'complete') {
console.log('Still ICE gathering - waiting...');
setTimeout(getAllIceCandidates, 100);
return;
}
returnToTest(JSON.stringify(gIceCandidates));
}
/**
* Receives ICE candidates from the peer.
*
* Returns ok-received-candidates to the test on success.
*
* @param iceCandidatesJson a JSON-encoded array of RTCIceCandidate instances.
*/
function receiveIceCandidates(iceCandidatesJson) {
var iceCandidates = parseJson_(iceCandidatesJson);
if (!iceCandidates.length)
throw failTest('Received invalid ICE candidate list from peer: ' +
iceCandidatesJson);
iceCandidates.forEach(function(iceCandidate) {
if (!iceCandidate.candidate)
failTest('Received invalid ICE candidate from peer: ' +
iceCandidatesJson);
peerConnection_().addIceCandidate(new RTCIceCandidate(iceCandidate,
function() { success('addIceCandidate'); },
function(error) { failure('addIceCandidate', error); }
));
});
returnToTest('ok-received-candidates');
}
/**
* Sets the mute state of the selected media element.
*
* Returns ok-muted on success.
*
* @param elementId The id of the element to mute.
* @param muted The mute state to set.
*/
function setMediaElementMuted(elementId, muted) {
var element = document.getElementById(elementId);
if (!element)
throw failTest('Cannot mute ' + elementId + '; does not exist.');
element.muted = muted;
returnToTest('ok-muted');
}
/**
* Returns
*/
function hasSeenCryptoInSdp() {
returnToTest(gHasSeenCryptoInSdp);
}
// Internals.
/** @private */
function createPeerConnection_() {
try {
peerConnection = new RTCPeerConnection(null, {});
} catch (exception) {
throw failTest('Failed to create peer connection: ' + exception);
}
peerConnection.onaddstream = addStreamCallback_;
peerConnection.onremovestream = removeStreamCallback_;
peerConnection.onicecandidate = iceCallback_;
return peerConnection;
}
/** @private */
function peerConnection_() {
if (gPeerConnection == null)
throw failTest('Trying to use peer connection, but none was created.');
return gPeerConnection;
}
/** @private */
function iceCallback_(event) {
if (event.candidate)
gIceCandidates.push(event.candidate);
}
/** @private */
function setLocalDescription(peerConnection, sessionDescription) {
if (sessionDescription.sdp.search('a=crypto') != -1 ||
sessionDescription.sdp.search('a=fingerprint') != -1)
gHasSeenCryptoInSdp = 'crypto-seen';
peerConnection.setLocalDescription(
sessionDescription,
function() { success('setLocalDescription'); },
function(error) { failure('setLocalDescription', error); });
}
/** @private */
function addStreamCallback_(event) {
debug('Receiving remote stream...');
var videoTag = document.getElementById('remote-view');
attachMediaStream(videoTag, event.stream);
}
/** @private */
function removeStreamCallback_(event) {
debug('Call ended.');
document.getElementById('remote-view').src = '';
}
/**
* Parses JSON-encoded session descriptions and ICE candidates.
* @private
*/
function parseJson_(json) {
// Escape since the \r\n in the SDP tend to get unescaped.
jsonWithEscapedLineBreaks = json.replace(/\r\n/g, '\\r\\n');
try {
return JSON.parse(jsonWithEscapedLineBreaks);
} catch (exception) {
failTest('Failed to parse JSON: ' + jsonWithEscapedLineBreaks + ', got ' +
exception);
}
}