// Default transform functions, overridden by some test cases.
var transformSdp = function(sdp) { return sdp; };
var transformRemoteSdp = function(sdp) { return sdp; };
- var transformCandidate = function(candidate) { return candidate; };
- var onLocalDescriptionError = function(error) { };
+ var onLocalDescriptionError = function(error) { failTest(error); };
+ var onRemoteDescriptionError = function(error) { failTest(error); };
// Temporary measure to be able to force iSAC 16K where needed, particularly
// on Android. This applies to every test which is why it's implemented like
var maybeForceIsac16K = function(sdp) { return sdp; };
function forceIsac16KInSdp() {
maybeForceIsac16K = function(sdp) {
- // Remove all other codecs (not the video codecs though). Also leave
- // 126 for DTMF.
sdp = sdp.replace(/m=audio (\d+) RTP\/SAVPF.*\r\n/g,
'm=audio $1 RTP/SAVPF 103 126\r\n');
sdp = sdp.replace('a=fmtp:111 minptime=10', 'a=fmtp:103 minptime=10');
- sdp = sdp.replace(
- /a=rtpmap:(?!(103|126))\d{1,3} (?!X?VP8|red|ulpfec).*\r\n/g,
- '');
+ if (sdp.search('a=rtpmap:103 ISAC/16000') == -1)
+ failTest('Missing iSAC 16K codec on Android; cannot force codec.');
return sdp;
};
+ sendValueToTest('isac-forced');
}
// When using external SDES, the crypto key is chosen by javascript.
'inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj'
};
- // When using GICE, the ICE credentials can be chosen by javascript.
- var EXTERNAL_GICE_UFRAG = '1234567890123456';
- var EXTERNAL_GICE_PWD = '123456789012345678901234';
-
- setAllEventsOccuredHandler(function() {
- // The C++ tests look for this 'OK' in the title.
- document.title = 'OK';
- });
+ setAllEventsOccuredHandler(reportTestSuccess);
// Test that we can setup call with an audio and video track.
function call(constraints) {
waitForVideo('remote-view-2');
}
+ // Test that we can setup call with an audio and video track and check that
+ // the video resolution is as expected.
+ function callAndExpectResolution(constraints,
+ expected_width,
+ expected_height) {
+ createConnections(null);
+ navigator.webkitGetUserMedia(constraints,
+ addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
+ waitForVideoWithResolution('remote-view-1',
+ expected_width,
+ expected_height);
+ waitForVideoWithResolution('remote-view-2',
+ expected_width,
+ expected_height);
+ }
+
+
// First calls without streams on any connections, and then adds a stream
// to peer connection 1 which gets sent to peer connection 2. We must wait
// for the first negotiation to complete before starting the second one, which
}
// First makes a call between pc1 and pc2, and then makes a call between pc3
- // and pc4 where the remote streams from pc1 and pc2 will be used as the local
- // streams of pc3 and pc4.
+ // and pc4. The stream sent from pc3 to pc4 is the stream received on pc1.
+ // The stream sent from pc4 to pc3 is cloned from the stream received on pc2
+ // to test that cloning of remote video tracks works as intended.
function callAndForwardRemoteStream(constraints) {
createConnections(null);
navigator.webkitGetUserMedia(constraints,
thirdConnection.addStream(gRemoteStreams['remote-view-1']);
fourthConnection = createConnection(null, 'remote-view-4');
- fourthConnection.addStream(gRemoteStreams['remote-view-2']);
+ fourthConnection.addStream(gRemoteStreams['remote-view-2'].clone());
negotiateBetween(thirdConnection, fourthConnection);
detectVideoPlaying('remote-view-2', onRemoteStream2);
}
+ // First makes a call between pc1 and pc2, and then construct a new media
+ // stream using the remote audio and video tracks, connect the new media
+ // stream to a video element. These operations should not crash Chrome.
+ function ConnectChromiumSinkToRemoteAudioTrack() {
+ createConnections(null);
+ navigator.webkitGetUserMedia({audio: true, video: true},
+ addStreamToBothConnectionsAndNegotiate,
+ printGetUserMediaError);
+
+ detectVideoPlaying('remote-view-2', function() {
+ // Construct a new media stream with remote tracks.
+ var newStream = new webkitMediaStream();
+ newStream.addTrack(
+ gSecondConnection.getRemoteStreams()[0].getAudioTracks()[0]);
+ newStream.addTrack(
+ gSecondConnection.getRemoteStreams()[0].getVideoTracks()[0]);
+ var videoElement = document.createElement('video');
+
+ // No crash for this operation.
+ videoElement.src = URL.createObjectURL(newStream);
+ waitForVideo('remote-view-2');
+ });
+ }
+
// Test that we can setup call with an audio and video track and
// simulate that the remote peer don't support MSID.
function callWithoutMsidAndBundle() {
function negotiateUnsupportedVideoCodec() {
createConnections(null);
transformSdp = removeVideoCodec;
+
onLocalDescriptionError = function(error) {
var expectedMsg = 'Failed to set local offer sdp:' +
' Session error code: ERROR_CONTENT. Session error description:' +
' Failed to set video receive codecs..';
- expectEquals(expectedMsg, error);
-
- // Got the right message, test succeeded.
- document.title = 'OK';
+ assertEquals(expectedMsg, error);
+ reportTestSuccess();
};
navigator.webkitGetUserMedia({audio: true, video: true},
addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
transformSdp = removeCrypto;
onLocalDescriptionError = function(error) {
var expectedMsg = 'Failed to set local offer sdp:' +
- ' Called with a SDP without crypto enabled.';
- expectEquals(expectedMsg, error);
+ ' Called with SDP without DTLS fingerprint.';
- // Got the right message, test succeeded.
- document.title = 'OK';
+ assertEquals(expectedMsg, error);
+ reportTestSuccess();
};
navigator.webkitGetUserMedia({audio: true, video: true},
addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
createConnections(null);
transformSdp = addBandwithControl;
navigator.webkitGetUserMedia({audio: true, video: true},
- addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
+ addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
waitForVideo('remote-view-1');
waitForVideo('remote-view-2');
}
transformSdp = function(sdp) {
return removeBundle(useGice(useExternalSdes(sdp)));
};
- transformCandidate = addGiceCredsToCandidate;
createConnections({
'mandatory': {'RtpDataChannels': true, 'DtlsSrtpKeyAgreement': false}
});
// Set an event handler for when the data channel has been closed.
setAllEventsOccuredHandler(function() {
// When the video is flowing the test is done.
- setAllEventsOccuredHandler(function() {
- document.title = 'OK';
- });
+ setAllEventsOccuredHandler(reportTestSuccess);
navigator.webkitGetUserMedia({audio: true, video: true},
addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
waitForVideo('remote-view-1');
dtmfSender.ontonechange = onToneChange;
dtmfSender.insertDTMF(tones);
// Wait for the DTMF tones callback.
- document.title = 'Waiting for dtmf...';
addExpectedEvent();
var waitDtmf = setInterval(function() {
if (gSentTones == tones) {
detectVideoPlaying('remote-view-2', onCallEstablished);
}
+ function testCreateOfferOptions() {
+ createConnections(null);
+ var offerOptions = {
+ 'offerToReceiveAudio': false,
+ 'offerToReceiveVideo': true
+ };
+
+ gFirstConnection.createOffer(
+ function(offer) {
+ assertEquals(-1, offer.sdp.search('m=audio'));
+ assertNotEquals(-1, offer.sdp.search('m=video'));
+
+ reportTestSuccess();
+ },
+ function(error) { failTest(error); },
+ offerOptions);
+ }
+
function enableRemoteVideo(peerConnection, enabled) {
remoteStream = peerConnection.getRemoteStreams()[0];
remoteVideoTrack = remoteStream.getVideoTracks()[0];
remoteAudioTrack.enabled = enabled;
}
- function callAndEnsureAudioIsPlaying() {
+ function callAndEnsureAudioIsPlaying(beLenient, constraints) {
createConnections(null);
- navigator.webkitGetUserMedia({audio: true, video: true},
+ navigator.webkitGetUserMedia(constraints,
addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
// Wait until we have gathered samples and can conclude if audio is playing.
addExpectedEvent();
var onCallEstablished = function() {
- gatherAudioLevelSamples(gSecondConnection, 300, 100,
- function(samples) {
- verifyAudioIsPlaying(samples);
+ // Gather 50 samples per second for 2 seconds.
+ gatherAudioLevelSamples(gSecondConnection, 100, 50, function(samples) {
+ verifyAudioIsPlaying(samples, beLenient);
eventOccured();
});
-
- // (Also, ensure video muting doesn't affect audio).
- enableRemoteVideo(gSecondConnection, false);
};
- detectVideoPlaying('remote-view-2', onCallEstablished);
+ waitForConnectionToStabilize(gFirstConnection, onCallEstablished);
}
- function callAndEnsureAudioMutingWorks() {
- callAndEnsureAudioIsPlaying();
+ function callAndEnsureAudioTrackMutingWorks(beLenient) {
+ callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
setAllEventsOccuredHandler(function() {
// Call is up, now mute the track and check everything goes silent (give
// it a small delay though, we don't expect it to happen instantly).
enableRemoteAudio(gSecondConnection, false);
setTimeout(function() {
- gatherAudioLevelSamples(gSecondConnection, 200, 100, function(samples) {
+ gatherAudioLevelSamples(gSecondConnection, 100, 50, function(samples) {
verifyIsSilent(samples);
- document.title = 'OK';
+ reportTestSuccess();
});
}, 500);
});
}
- function callAndEnsureVideoMutingWorks() {
+ function callAndEnsureAudioTrackUnmutingWorks(beLenient) {
+ callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
+ setAllEventsOccuredHandler(function() {
+ // Mute, wait a while, unmute, verify audio gets back up.
+ // (Also, ensure video muting doesn't affect audio).
+ enableRemoteAudio(gSecondConnection, false);
+ enableRemoteVideo(gSecondConnection, false);
+
+ setTimeout(function() {
+ enableRemoteAudio(gSecondConnection, true);
+ }, 500);
+
+ setTimeout(function() {
+ // Sample for four seconds here; it can take a bit of time for audio to
+ // get back up after the unmute.
+ gatherAudioLevelSamples(gSecondConnection, 200, 50, function(samples) {
+ verifyAudioIsPlaying(samples, beLenient);
+ reportTestSuccess();
+ });
+ }, 1500);
+ });
+ }
+
+ function callAndEnsureVideoTrackMutingWorks() {
createConnections(null);
navigator.webkitGetUserMedia({audio: true, video: true},
addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
// received.
addExpectedEvent();
remote_stream_1.onaddtrack = function(){
- expectEquals(remote_stream_1.getAudioTracks()[0].id,
+ assertEquals(remote_stream_1.getAudioTracks()[0].id,
local_stream.getAudioTracks()[0].id);
eventOccured();
}
remote_stream_2 = gSecondConnection.getRemoteStreams()[0];
addExpectedEvent();
remote_stream_2.onaddtrack = function() {
- expectEquals(remote_stream_2.getAudioTracks()[0].id,
+ assertEquals(remote_stream_2.getAudioTracks()[0].id,
local_stream.getAudioTracks()[0].id);
eventOccured();
}
eventOccured();
}
// When all the above events have occurred- the test pass.
- setAllEventsOccuredHandler(function() { document.title = 'OK'; });
+ setAllEventsOccuredHandler(reportTestSuccess);
local_stream.addTrack(gLocalStream.getAudioTracks()[0]);
local_stream.removeTrack(local_stream.getVideoTracks()[0]);
var sendDataString = "send some text on a data channel."
firstDataChannel = gFirstConnection.createDataChannel(
"sendDataChannel", params);
- expectEquals('connecting', firstDataChannel.readyState);
+ assertEquals('connecting', firstDataChannel.readyState);
// When |firstDataChannel| transition to open state, send a text string.
firstDataChannel.onopen = function() {
- expectEquals('open', firstDataChannel.readyState);
- firstDataChannel.send(sendDataString);
+ assertEquals('open', firstDataChannel.readyState);
+ if (firstDataChannel.reliable) {
+ firstDataChannel.send(sendDataString);
+ } else {
+ sendDataRepeatedlyUntilClosed(firstDataChannel);
+ }
}
// When |firstDataChannel| receive a message, close the channel and
// initiate a new offer/answer exchange to complete the closure.
firstDataChannel.onmessage = function(event) {
- expectEquals(event.data, sendDataString);
+ assertEquals(event.data, sendDataString);
firstDataChannel.close();
negotiate();
}
// When |firstDataChannel| transition to closed state, the test pass.
addExpectedEvent();
firstDataChannel.onclose = function() {
- expectEquals('closed', firstDataChannel.readyState);
+ assertEquals('closed', firstDataChannel.readyState);
eventOccured();
}
// When |secondDataChannel| receive a message, send a message back.
secondDataChannel.onmessage = function(event) {
- expectEquals(event.data, sendDataString);
- expectEquals('open', secondDataChannel.readyState);
- secondDataChannel.send(sendDataString);
+ assertEquals(event.data, sendDataString);
+ console.log("gSecondConnection received data");
+ if (secondDataChannel.reliable) {
+ // If we're reliable we will just send one message over the channel,
+ // and therefore channel one's message handler cannot have shut us
+ // down already.
+ assertEquals('open', secondDataChannel.readyState);
+ secondDataChannel.send(sendDataString);
+ } else {
+ // If unreliable, this could be one in a series of messages and it
+ // is possible we already replied (which will close our channel).
+ sendDataRepeatedlyUntilClosed(secondDataChannel);
+ }
}
}
+
+ // Sends |sendDataString| on |dataChannel| every 200ms as long as
+ // |dataChannel| is open.
+ function sendDataRepeatedlyUntilClosed(dataChannel) {
+ var sendTimer = setInterval(function() {
+ if (dataChannel.readyState == 'open')
+ dataChannel.send(sendDataString);
+ else
+ clearInterval(sendTimer);
+ }, 200);
+ }
}
// SCTP data channel setup is slightly different then RTP based
var sendDataString = "send some text on a data channel."
firstDataChannel = gFirstConnection.createDataChannel(
"sendDataChannel", params);
- expectEquals('connecting', firstDataChannel.readyState);
+ assertEquals('connecting', firstDataChannel.readyState);
// When |firstDataChannel| transition to open state, send a text string.
firstDataChannel.onopen = function() {
- expectEquals('open', firstDataChannel.readyState);
+ 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) {
- expectEquals('open', firstDataChannel.readyState);
- expectEquals(event.data, sendDataString);
+ assertEquals('open', firstDataChannel.readyState);
+ assertEquals(event.data, sendDataString);
firstDataChannel.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) {
- expectEquals(event.data, sendDataString);
- expectEquals('open', secondDataChannel.readyState);
+ assertEquals(event.data, sendDataString);
+ assertEquals('open', secondDataChannel.readyState);
secondDataChannel.close();
negotiate();
}
// When |secondDataChannel| transition to closed state, the test pass.
addExpectedEvent();
secondDataChannel.onclose = function() {
- expectEquals('closed', secondDataChannel.readyState);
+ assertEquals('closed', secondDataChannel.readyState);
eventOccured();
}
}
function onToneChange(tone) {
gSentTones += tone.tone;
- document.title = gSentTones;
}
function createConnections(constraints) {
gFirstConnection = createConnection(constraints, 'remote-view-1');
- expectEquals('stable', gFirstConnection.signalingState);
+ assertEquals('stable', gFirstConnection.signalingState);
gSecondConnection = createConnection(constraints, 'remote-view-2');
- expectEquals('stable', gSecondConnection.signalingState);
+ assertEquals('stable', gSecondConnection.signalingState);
}
function createConnection(constraints, remoteView) {
// Called if getUserMedia fails.
function printGetUserMediaError(error) {
- document.title = 'getUserMedia request failed:';
+ var message = 'getUserMedia request unexpectedly failed:';
if (error.constraintName)
- document.title += ' could not satisfy constraint ' + error.constraintName;
+ message += ' could not satisfy constraint ' + error.constraintName;
else
- document.title += ' devices not working/user denied access.';
- console.log(document.title);
+ message += ' devices not working/user denied access.';
+ failTest(message);
}
// Called if getUserMedia succeeds and we want to send from both connections.
}
function verifyHasOneAudioAndVideoTrack(stream) {
- expectEquals(1, stream.getAudioTracks().length);
- expectEquals(1, stream.getVideoTracks().length);
+ assertEquals(1, stream.getAudioTracks().length);
+ assertEquals(1, stream.getVideoTracks().length);
}
// Called if getUserMedia succeeds, then clone the stream, send two streams
gFirstConnection.addStream(clonedStream);
// Verify the local streams are correct.
- expectEquals(2, gFirstConnection.getLocalStreams().length);
+ assertEquals(2, gFirstConnection.getLocalStreams().length);
verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[0]);
verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[1]);
}
setAllEventsOccuredHandler(function() {
// Negotiation complete, verify remote streams on the receiving side.
- expectEquals(2, gSecondConnection.getRemoteStreams().length);
+ assertEquals(2, gSecondConnection.getRemoteStreams().length);
verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[0]);
verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[1]);
- document.title = "OK";
+ reportTestSuccess();
});
negotiate();
console.log("Negotiating call...");
// Not stable = negotiation is ongoing. The behavior of re-negotiating while
// a negotiation is ongoing is more or less undefined, so avoid this.
- if (caller.signalingState != 'stable')
+ if (caller.signalingState != 'stable' || callee.signalingState != 'stable')
throw 'You can only negotiate when the connection is stable!';
connectOnIceCandidate(caller, callee);
function onOfferCreated(offer, caller, callee) {
offer.sdp = maybeForceIsac16K(transformSdp(offer.sdp));
caller.setLocalDescription(offer, function() {
- expectEquals('have-local-offer', caller.signalingState);
+ assertEquals('have-local-offer', caller.signalingState);
receiveOffer(offer.sdp, caller, callee);
}, onLocalDescriptionError);
}
function receiveOffer(offerSdp, caller, callee) {
- console.log("Receiving offer...");
+ console.log("Receiving offer...\n" + offerSdp);
offerSdp = transformRemoteSdp(offerSdp);
var parsedOffer = new RTCSessionDescription({ type: 'offer',
sdp: offerSdp });
- callee.setRemoteDescription(parsedOffer);
+ callee.setRemoteDescription(parsedOffer, function() {},
+ onRemoteDescriptionError);
callee.createAnswer(function (answer) {
onAnswerCreated(answer, caller, callee);
});
- expectEquals('have-remote-offer', callee.signalingState);
+ assertEquals('have-remote-offer', callee.signalingState);
}
function removeMsid(offerSdp) {
'b=AS:512\r\n');
return offerSdp;
}
-
+
function removeBundle(sdp) {
return sdp.replace(/a=group:BUNDLE .*\r\n/g, '');
}
sdp = sdp.replace(/t=.*\r\n/g, function(subString) {
return subString + 'a=ice-options:google-ice\r\n';
});
- sdp = sdp.replace(/a=ice-ufrag:.*\r\n/g,
- 'a=ice-ufrag:' + EXTERNAL_GICE_UFRAG + '\r\n');
- sdp = sdp.replace(/a=ice-pwd:.*\r\n/g,
- 'a=ice-pwd:' + EXTERNAL_GICE_PWD + '\r\n');
return sdp;
}
function onAnswerCreated(answer, caller, callee) {
answer.sdp = maybeForceIsac16K(transformSdp(answer.sdp));
- callee.setLocalDescription(answer);
- expectEquals('stable', callee.signalingState);
+ callee.setLocalDescription(answer,
+ function () {
+ assertEquals('stable', callee.signalingState);
+ },
+ onLocalDescriptionError);
receiveAnswer(answer.sdp, caller);
}
answerSdp = transformRemoteSdp(answerSdp);
var parsedAnswer = new RTCSessionDescription({ type: 'answer',
sdp: answerSdp });
- caller.setRemoteDescription(parsedAnswer);
- expectEquals('stable', caller.signalingState);
+ caller.setRemoteDescription(parsedAnswer,
+ function() {
+ assertEquals('stable', caller.signalingState);
+ },
+ onRemoteDescriptionError);
}
function connectOnIceCandidate(caller, callee) {
callee.onicecandidate = function(event) { onIceCandidate(event, caller); }
}
- function addGiceCredsToCandidate(candidate) {
- return candidate.trimRight() +
- ' username ' + EXTERNAL_GICE_UFRAG + ' password ' + EXTERNAL_GICE_PWD;
- }
-
function onIceCandidate(event, target) {
if (event.candidate) {
var candidate = new RTCIceCandidate(event.candidate);
- candidate.candidate = transformCandidate(candidate.candidate);
target.addIceCandidate(candidate);
}
}
function onRemoteStream(e, target) {
console.log("Receiving remote stream...");
if (gTestWithoutMsid && e.stream.id != "default") {
- document.title = 'a default remote stream was expected but instead ' +
- e.stream.id + ' was received.';
- return;
+ failTest('a default remote stream was expected but instead ' +
+ e.stream.id + ' was received.');
}
gRemoteStreams[target] = e.stream;
var remoteStreamUrl = URL.createObjectURL(e.stream);