<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);
};
var gFirstConnection = null;
var gSecondConnection = null;
var gLocalStream = null;
var gRemoteStreams = {};
function callWithSctpDataOnly() {
createConnections();
return Promise.all([
promiseSctpDataChannelExchange({reliable: true}),
negotiate(),
])
.then(logSuccess);
}
function callWithSctpDataAndMedia() {
createConnections();
var hasExchanged = promiseSctpDataChannelExchange({reliable: true});
return Promise.all([
navigator.mediaDevices.getUserMedia({audio: true, video: true})
.then(addStreamToBothConnectionsAndNegotiate),
hasExchanged,
detectVideoPlaying('remote-view-1'),
detectVideoPlaying('remote-view-2')
])
.then(logSuccess);
}
// 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() {
gFirstConnection = createConnection('remote-view-1');
assertEquals('stable', gFirstConnection.signalingState);
gSecondConnection = createConnection('remote-view-2');
assertEquals('stable', gSecondConnection.signalingState);
}
function createConnection(remoteView) {
var pc = new RTCPeerConnection();
pc.onaddstream = function(event) {
onRemoteStream(event, remoteView);
}
return pc;
}
function displayAndRemember(localStream) {
$('local-view').srcObject = localStream;
gLocalStream = localStream;
}
function negotiate() {
Promise.all([
waitForConnectionToStabilizeIfNeeded(gFirstConnection),
waitForConnectionToStabilizeIfNeeded(gSecondConnection),
]).then(() => {
negotiateBetween(gFirstConnection, gSecondConnection);
});
}
function onRemoteStream(e, target) {
console.log("Receiving remote stream...");
gRemoteStreams[target] = e.stream;
var remoteVideo = $(target);
remoteVideo.srcObject = e.stream;
}
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, '');
}
</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>