blob: 048d3bfab7883fabc3ee9b29706d488f05c3965b [file] [log] [blame]
<html>
<head>
<script type="text/javascript" src="webrtc_test_utilities.js"></script>
<script type="text/javascript" src="webrtc_test_common.js"></script>
<script type="text/javascript">
$ = function(id) {
return document.getElementById(id);
};
window.onerror = function(errorMsg, url, lineNumber, column, errorObj) {
failTest('Error: ' + errorMsg + '\nScript: ' + url +
'\nLine: ' + lineNumber + '\nColumn: ' + column +
'\nStackTrace: ' + errorObj);
}
var gFirstConnection = null;
var gSecondConnection = null;
var gLocalStream = null;
var gRemoteStreams = {};
// When using external SDES, the crypto key is chosen by javascript.
var EXTERNAL_SDES_LINES = {
'audio': 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' +
'inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR',
'video': 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' +
'inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj',
'data': 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' +
'inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj'
};
// Test that we can setup call with legacy settings.
function callWithLegacySdp() {
setOfferSdpTransform(function(sdp) {
return removeBundle(useGice(useExternalSdes(sdp)));
});
createConnections({
'mandatory': {'RtpDataChannels': true, 'DtlsSrtpKeyAgreement': false}
});
var hasExchanged = promiseDataChannelExchange({reliable: false});
navigator.mediaDevices.getUserMedia({audio: true, video: true})
.then(addStreamToBothConnectionsAndNegotiate)
.catch(failTest);
Promise.all([
hasExchanged,
detectVideoPlaying('remote-view-1'),
detectVideoPlaying('remote-view-2')
]).then(reportTestSuccess);
}
// Test only a data channel.
function callWithDataOnly() {
createConnections({optional:[{RtpDataChannels: true}]});
promiseDataChannelExchange({reliable: false})
.then(reportTestSuccess);
negotiate();
}
function callWithSctpDataOnly() {
createConnections({optional: [{DtlsSrtpKeyAgreement: true}]});
promiseSctpDataChannelExchange({reliable: true})
.then(reportTestSuccess);
negotiate();
}
// Test call with audio, video and a data channel.
function callWithDataAndMedia() {
createConnections({optional:[{RtpDataChannels: true}]});
var hasExchanged = promiseDataChannelExchange({reliable: false});
navigator.mediaDevices.getUserMedia({audio: true, video: true})
.then(addStreamToBothConnectionsAndNegotiate)
.catch(failTest);
Promise.all([
hasExchanged,
detectVideoPlaying('remote-view-1'),
detectVideoPlaying('remote-view-2')
]).then(reportTestSuccess);
}
function callWithSctpDataAndMedia() {
createConnections({optional: [{DtlsSrtpKeyAgreement: true}]});
var hasExchanged = promiseSctpDataChannelExchange({reliable: true});
navigator.mediaDevices.getUserMedia({audio: true, video: true})
.then(addStreamToBothConnectionsAndNegotiate)
.catch(failTest);
Promise.all([
hasExchanged,
detectVideoPlaying('remote-view-1'),
detectVideoPlaying('remote-view-2')
]).then(reportTestSuccess);
}
// Test call with a data channel and later add audio and video.
function callWithDataAndLaterAddMedia() {
createConnections({optional:[{RtpDataChannels: true}]});
var hasExchanged = promiseDataChannelExchange({reliable: false});
negotiate();
// Set an event handler for when the data channel has been closed.
hasExchanged.then(() => {
// When the video is flowing the test is done.
return navigator.mediaDevices.getUserMedia({audio: true, video: true})
.then(addStreamToBothConnectionsAndNegotiate);
}).then(() => {
return Promise.all([
detectVideoPlaying('remote-view-1'),
detectVideoPlaying('remote-view-2')
]);
})
.then(reportTestSuccess)
.catch(failTest);
}
// This function is used for setting up a test that:
// 1. Creates a data channel on |gFirstConnection| and sends data to
// |gSecondConnection|.
// 2. When data is received on |gSecondConnection| a message
// is sent to |gFirstConnection|.
// 3. When data is received on |gFirstConnection|, the data
// channel is closed. This function returns a promise that resolves when
// that last channel is closed.
//
// Note: you need to negotiate after calling this function, or the exchange
// will not happen, and the promise will not resolve.
function promiseDataChannelExchange(params) {
var sendDataString = "send some text on a data channel."
firstDataChannel = gFirstConnection.createDataChannel(
"sendDataChannel", params);
assertEquals('connecting', firstDataChannel.readyState);
// When |firstDataChannel| transition to open state, send a text string.
firstDataChannel.onopen = function() {
assertEquals('open', firstDataChannel.readyState);
firstDataChannel.send(sendDataString);
}
// When |firstDataChannel| receive a message, close the channel and
// initiate a new offer/answer exchange to complete the closure.
firstDataChannel.onmessage = function(event) {
assertEquals(event.data, sendDataString);
firstDataChannel.close();
negotiate();
}
// When |firstDataChannel| transition to closed state, the test pass.
var closedPromise = new Promise((resolve, reject) => {
firstDataChannel.onclose = function() {
assertEquals('closed', firstDataChannel.readyState);
resolve();
}
});
// Event handler for when |gSecondConnection| receive a new dataChannel.
gSecondConnection.ondatachannel = function (event) {
// Make secondDataChannel global to make sure it's not gc'd.
secondDataChannel = event.channel;
// When |secondDataChannel| receive a message, send a message back.
secondDataChannel.onmessage = function(event) {
assertEquals(event.data, sendDataString);
console.log("gSecondConnection received data");
assertEquals('open', secondDataChannel.readyState);
secondDataChannel.send(sendDataString);
}
}
return closedPromise;
}
// SCTP data channel setup is slightly different then RTP based
// channels. Due to a bug in libjingle, we can't send data immediately
// after channel becomes open. So for that reason in SCTP,
// we are sending data from second channel, when ondatachannel event is
// received. So data flow happens 2 -> 1 -> 2.
// Note: you need to negotiate after calling this function, or the exchange
// will not happen, and the promise will not resolve.
function promiseSctpDataChannelExchange(params) {
var sendDataString = "send some text on a data channel."
firstDataChannel = gFirstConnection.createDataChannel(
"sendDataChannel", params);
assertEquals('connecting', firstDataChannel.readyState);
// When |firstDataChannel| transition to open state, send a text string.
firstDataChannel.onopen = function() {
assertEquals('open', firstDataChannel.readyState);
}
// When |firstDataChannel| receive a message, send message back.
// initiate a new offer/answer exchange to complete the closure.
firstDataChannel.onmessage = function(event) {
assertEquals('open', firstDataChannel.readyState);
assertEquals(event.data, sendDataString);
firstDataChannel.send(sendDataString);
}
return new Promise((resolve, reject) => {
// Event handler for when |gSecondConnection| receive a new dataChannel.
gSecondConnection.ondatachannel = function (event) {
// Make secondDataChannel global to make sure it's not gc'd.
secondDataChannel = event.channel;
secondDataChannel.onopen = function() {
secondDataChannel.send(sendDataString);
}
// When |secondDataChannel| receive a message, close the channel and
// initiate a new offer/answer exchange to complete the closure.
secondDataChannel.onmessage = function(event) {
assertEquals(event.data, sendDataString);
assertEquals('open', secondDataChannel.readyState);
secondDataChannel.close();
negotiate();
}
// When |secondDataChannel| transition to closed state, we're done.
secondDataChannel.onclose = function() {
assertEquals('closed', secondDataChannel.readyState);
resolve();
}
}
});
}
function addStreamToBothConnectionsAndNegotiate(localStream) {
displayAndRemember(localStream);
gFirstConnection.addStream(localStream);
gSecondConnection.addStream(localStream);
negotiate();
}
function createConnections(constraints) {
gFirstConnection = createConnection(constraints, 'remote-view-1');
assertEquals('stable', gFirstConnection.signalingState);
gSecondConnection = createConnection(constraints, 'remote-view-2');
assertEquals('stable', gSecondConnection.signalingState);
}
function createConnection(constraints, remoteView) {
var pc = new RTCPeerConnection(null, constraints);
pc.onaddstream = function(event) {
onRemoteStream(event, remoteView);
}
return pc;
}
function displayAndRemember(localStream) {
var localStreamUrl = URL.createObjectURL(localStream);
$('local-view').src = localStreamUrl;
gLocalStream = localStream;
}
function negotiate() {
negotiateBetween(gFirstConnection, gSecondConnection);
}
function onRemoteStream(e, target) {
console.log("Receiving remote stream...");
gRemoteStreams[target] = e.stream;
var remoteStreamUrl = URL.createObjectURL(e.stream);
var remoteVideo = $(target);
remoteVideo.src = remoteStreamUrl;
}
function connectOnIceCandidate(caller, callee) {
caller.onicecandidate = function(event) { onIceCandidate(event, callee); }
callee.onicecandidate = function(event) { onIceCandidate(event, caller); }
}
function onIceCandidate(event, target) {
if (event.candidate) {
var candidate = new RTCIceCandidate(event.candidate);
target.addIceCandidate(candidate);
}
}
function removeBundle(sdp) {
return sdp.replace(/a=group:BUNDLE .*\r\n/g, '');
}
function useGice(sdp) {
sdp = sdp.replace(/t=.*\r\n/g, function(subString) {
return subString + 'a=ice-options:google-ice\r\n';
});
return sdp;
}
function useExternalSdes(sdp) {
// Remove current crypto specification.
sdp = sdp.replace(/a=crypto.*\r\n/g, '');
sdp = sdp.replace(/a=fingerprint.*\r\n/g, '');
// Add external crypto. This is not compatible with |removeMsid|.
sdp = sdp.replace(/a=mid:(\w+)\r\n/g, function(subString, group) {
return subString + EXTERNAL_SDES_LINES[group] + '\r\n';
});
return sdp;
}
</script>
</head>
<body>
<table border="0">
<tr>
<td><video width="320" height="240" id="local-view" style="display:none"
autoplay muted></video></td>
<td><video width="320" height="240" id="remote-view-1"
style="display:none" autoplay></video></td>
<td><video width="320" height="240" id="remote-view-2"
style="display:none" autoplay></video></td>
<!-- Canvases are named after their corresponding video elements. -->
<td><canvas width="320" height="240" id="remote-view-1-canvas"
style="display:none"></canvas></td>
<td><canvas width="320" height="240" id="remote-view-2-canvas"
style="display:none"></canvas></td>
</tr>
</table>
</body>
</html>