3 <script type="text/javascript" src="webrtc_test_utilities.js"></script>
4 <script type="text/javascript" src="webrtc_test_audio.js"></script>
5 <script type="text/javascript">
7 return document.getElementById(id);
10 window.onerror = function(errorMsg, url, lineNumber, column, errorObj) {
11 failTest('Error: ' + errorMsg + '\nScript: ' + url +
12 '\nLine: ' + lineNumber + '\nColumn: ' + column +
13 '\nStackTrace: ' + errorObj);
16 var gFirstConnection = null;
17 var gSecondConnection = null;
18 var gTestWithoutMsid = false;
19 var gLocalStream = null;
22 var gRemoteStreams = {};
24 // Default transform functions, overridden by some test cases.
25 var transformSdp = function(sdp) { return sdp; };
26 var transformRemoteSdp = function(sdp) { return sdp; };
27 var onLocalDescriptionError = function(error) { failTest(error); };
28 var onRemoteDescriptionError = function(error) { failTest(error); };
30 // Temporary measure to be able to force iSAC 16K where needed, particularly
31 // on Android. This applies to every test which is why it's implemented like
33 var maybeForceIsac16K = function(sdp) { return sdp; };
34 function forceIsac16KInSdp() {
35 maybeForceIsac16K = function(sdp) {
36 if (sdp.search('m=audio') == -1)
39 sdp = sdp.replace(/m=audio (\d+) RTP\/SAVPF.*\r\n/g,
40 'm=audio $1 RTP/SAVPF 103 126\r\n');
41 sdp = sdp.replace('a=fmtp:111 minptime=10', 'a=fmtp:103 minptime=10');
42 if (sdp.search('a=rtpmap:103 ISAC/16000') == -1)
43 failTest('Missing iSAC 16K codec on Android; cannot force codec.');
47 sendValueToTest('isac-forced');
50 // When using external SDES, the crypto key is chosen by javascript.
51 var EXTERNAL_SDES_LINES = {
52 'audio': 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' +
53 'inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR',
54 'video': 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' +
55 'inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj',
56 'data': 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' +
57 'inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj'
60 setAllEventsOccuredHandler(reportTestSuccess);
62 // Test that we can setup call with an audio and video track.
63 function call(constraints) {
64 createConnections(null);
65 navigator.webkitGetUserMedia(constraints,
66 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
67 waitForVideo('remote-view-1');
68 waitForVideo('remote-view-2');
71 // Test that we can setup a call with a video track and that the remote peer
72 // receives black frames if the local video track is disabled.
73 function callAndDisableLocalVideo(constraints) {
74 createConnections(null);
75 navigator.webkitGetUserMedia(constraints,
76 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
77 detectVideoPlaying('remote-view-1',
79 assertEquals(gLocalStream.getVideoTracks().length, 1);
80 gLocalStream.getVideoTracks()[0].enabled = false;
81 waitForBlackVideo('remote-view-1');
85 // Test that we can setup call with an audio and video track and check that
86 // the video resolution is as expected.
87 function callAndExpectResolution(constraints,
90 createConnections(null);
91 navigator.webkitGetUserMedia(constraints,
92 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
93 waitForVideoWithResolution('remote-view-1',
96 waitForVideoWithResolution('remote-view-2',
102 // First calls without streams on any connections, and then adds a stream
103 // to peer connection 1 which gets sent to peer connection 2. We must wait
104 // for the first negotiation to complete before starting the second one, which
105 // is why we wait until the connection is stable before re-negotiating.
106 function callEmptyThenAddOneStreamAndRenegotiate(constraints) {
107 createConnections(null);
109 waitForConnectionToStabilize(gFirstConnection, function() {
110 navigator.webkitGetUserMedia(constraints,
111 addStreamToTheFirstConnectionAndNegotiate, printGetUserMediaError);
112 // Only the first connection is sending here.
113 waitForVideo('remote-view-2');
117 // First makes a call between pc1 and pc2, and then makes a call between pc3
118 // and pc4. The stream sent from pc3 to pc4 is the stream received on pc1.
119 // The stream sent from pc4 to pc3 is cloned from the stream received on pc2
120 // to test that cloning of remote video tracks works as intended.
121 function callAndForwardRemoteStream(constraints) {
122 createConnections(null);
123 navigator.webkitGetUserMedia(constraints,
124 addStreamToBothConnectionsAndNegotiate,
125 printGetUserMediaError);
126 var gotRemoteStream1 = false;
127 var gotRemoteStream2 = false;
129 var onRemoteStream1 = function() {
130 gotRemoteStream1 = true;
131 maybeCallEstablished();
134 var onRemoteStream2 = function() {
135 gotRemoteStream2 = true;
136 maybeCallEstablished();
139 var maybeCallEstablished = function() {
140 if (gotRemoteStream1 && gotRemoteStream2) {
145 var onCallEstablished = function() {
146 thirdConnection = createConnection(null, 'remote-view-3');
147 thirdConnection.addStream(gRemoteStreams['remote-view-1']);
149 fourthConnection = createConnection(null, 'remote-view-4');
150 fourthConnection.addStream(gRemoteStreams['remote-view-2'].clone());
152 negotiateBetween(thirdConnection, fourthConnection);
154 waitForVideo('remote-view-3');
155 waitForVideo('remote-view-4');
158 // Do the forwarding after we have received video.
159 detectVideoPlaying('remote-view-1', onRemoteStream1);
160 detectVideoPlaying('remote-view-2', onRemoteStream2);
163 // First makes a call between pc1 and pc2, and then construct a new media
164 // stream using the remote audio and video tracks, connect the new media
165 // stream to a video element. These operations should not crash Chrome.
166 function ConnectChromiumSinkToRemoteAudioTrack() {
167 createConnections(null);
168 navigator.webkitGetUserMedia({audio: true, video: true},
169 addStreamToBothConnectionsAndNegotiate,
170 printGetUserMediaError);
172 detectVideoPlaying('remote-view-2', function() {
173 // Construct a new media stream with remote tracks.
174 var newStream = new webkitMediaStream();
176 gSecondConnection.getRemoteStreams()[0].getAudioTracks()[0]);
178 gSecondConnection.getRemoteStreams()[0].getVideoTracks()[0]);
179 var videoElement = document.createElement('video');
181 // No crash for this operation.
182 videoElement.src = URL.createObjectURL(newStream);
183 waitForVideo('remote-view-2');
187 // Test that we can setup call with an audio and video track and
188 // simulate that the remote peer don't support MSID.
189 function callWithoutMsidAndBundle() {
190 createConnections(null);
191 transformSdp = removeBundle;
192 transformRemoteSdp = removeMsid;
193 gTestWithoutMsid = true;
194 navigator.webkitGetUserMedia({audio: true, video: true},
195 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
196 waitForVideo('remote-view-1');
197 waitForVideo('remote-view-2');
200 // Test that we can't setup a call with an unsupported video codec
201 function negotiateUnsupportedVideoCodec() {
202 createConnections(null);
203 transformSdp = removeVideoCodec;
205 onLocalDescriptionError = function(error) {
206 var expectedMsg = 'Failed to set local offer sdp:' +
207 ' Session error code: ERROR_CONTENT. Session error description:' +
208 ' Failed to set video receive codecs..';
209 assertEquals(expectedMsg, error);
212 navigator.webkitGetUserMedia({audio: true, video: true},
213 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
216 // Test that we can't setup a call if one peer does not support encryption
217 function negotiateNonCryptoCall() {
218 createConnections(null);
219 transformSdp = removeCrypto;
220 onLocalDescriptionError = function(error) {
221 var expectedMsg = 'Failed to set local offer sdp:' +
222 ' Called with SDP without DTLS fingerprint.';
224 assertEquals(expectedMsg, error);
227 navigator.webkitGetUserMedia({audio: true, video: true},
228 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
231 // Test that we can negotiate a call with an SDP offer that includes a
232 // b=AS:XX line to control audio and video bandwidth
233 function negotiateOfferWithBLine() {
234 createConnections(null);
235 transformSdp = addBandwithControl;
236 navigator.webkitGetUserMedia({audio: true, video: true},
237 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
238 waitForVideo('remote-view-1');
239 waitForVideo('remote-view-2');
242 // Test that we can setup call with legacy settings.
243 function callWithLegacySdp() {
244 transformSdp = function(sdp) {
245 return removeBundle(useGice(useExternalSdes(sdp)));
248 'mandatory': {'RtpDataChannels': true, 'DtlsSrtpKeyAgreement': false}
250 setupDataChannel({reliable: false});
251 navigator.webkitGetUserMedia({audio: true, video: true},
252 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
253 waitForVideo('remote-view-1');
254 waitForVideo('remote-view-2');
257 // Test only a data channel.
258 function callWithDataOnly() {
259 createConnections({optional:[{RtpDataChannels: true}]});
260 setupDataChannel({reliable: false});
264 function callWithSctpDataOnly() {
265 createConnections({optional: [{DtlsSrtpKeyAgreement: true}]});
266 setupSctpDataChannel({reliable: true});
270 // Test call with audio, video and a data channel.
271 function callWithDataAndMedia() {
272 createConnections({optional:[{RtpDataChannels: true}]});
273 setupDataChannel({reliable: false});
274 navigator.webkitGetUserMedia({audio: true, video: true},
275 addStreamToBothConnectionsAndNegotiate,
276 printGetUserMediaError);
277 waitForVideo('remote-view-1');
278 waitForVideo('remote-view-2');
281 function callWithSctpDataAndMedia() {
282 createConnections({optional: [{DtlsSrtpKeyAgreement: true}]});
283 setupSctpDataChannel({reliable: true});
284 navigator.webkitGetUserMedia({audio: true, video: true},
285 addStreamToBothConnectionsAndNegotiate,
286 printGetUserMediaError);
287 waitForVideo('remote-view-1');
288 waitForVideo('remote-view-2');
291 // Test call with a data channel and later add audio and video.
292 function callWithDataAndLaterAddMedia() {
293 createConnections({optional:[{RtpDataChannels: true}]});
294 setupDataChannel({reliable: false});
297 // Set an event handler for when the data channel has been closed.
298 setAllEventsOccuredHandler(function() {
299 // When the video is flowing the test is done.
300 setAllEventsOccuredHandler(reportTestSuccess);
301 navigator.webkitGetUserMedia({audio: true, video: true},
302 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
303 waitForVideo('remote-view-1');
304 waitForVideo('remote-view-2');
308 // Test that we can setup call and send DTMF.
309 function callAndSendDtmf(tones) {
310 createConnections(null);
311 navigator.webkitGetUserMedia({audio: true, video: true},
312 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
313 var onCallEstablished = function() {
315 var localAudioTrack = gLocalStream.getAudioTracks()[0];
316 var dtmfSender = gFirstConnection.createDTMFSender(localAudioTrack);
317 dtmfSender.ontonechange = onToneChange;
318 dtmfSender.insertDTMF(tones);
319 // Wait for the DTMF tones callback.
321 var waitDtmf = setInterval(function() {
322 if (gSentTones == tones) {
323 clearInterval(waitDtmf);
329 // Do the DTMF test after we have received video.
330 detectVideoPlaying('remote-view-2', onCallEstablished);
333 function testCreateOfferOptions() {
334 createConnections(null);
336 'offerToReceiveAudio': false,
337 'offerToReceiveVideo': true
340 gFirstConnection.createOffer(
342 assertEquals(-1, offer.sdp.search('m=audio'));
343 assertNotEquals(-1, offer.sdp.search('m=video'));
347 function(error) { failTest(error); },
351 function callAndEnsureAudioIsPlaying(beLenient, constraints) {
352 createConnections(null);
354 // Add the local stream to gFirstConnection to play one-way audio.
355 navigator.webkitGetUserMedia(constraints,
356 addStreamToTheFirstConnectionAndNegotiate, printGetUserMediaError);
358 var onCallEstablished = function() {
359 ensureAudioPlaying(gSecondConnection, beLenient);
362 waitForConnectionToStabilize(gFirstConnection, onCallEstablished);
365 function enableRemoteVideo(peerConnection, enabled) {
366 remoteStream = peerConnection.getRemoteStreams()[0];
367 remoteStream.getVideoTracks()[0].enabled = enabled;
370 function enableRemoteAudio(peerConnection, enabled) {
371 remoteStream = peerConnection.getRemoteStreams()[0];
372 remoteStream.getAudioTracks()[0].enabled = enabled;
375 function enableLocalVideo(peerConnection, enabled) {
376 localStream = peerConnection.getLocalStreams()[0];
377 localStream.getVideoTracks()[0].enabled = enabled;
380 function enableLocalAudio(peerConnection, enabled) {
381 localStream = peerConnection.getLocalStreams()[0];
382 localStream.getAudioTracks()[0].enabled = enabled;
385 function callAndEnsureRemoteAudioTrackMutingWorks(beLenient) {
386 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
387 setAllEventsOccuredHandler(function() {
388 setAllEventsOccuredHandler(reportTestSuccess);
390 // Call is up, now mute the remote track and check we stop playing out
391 // audio (after a small delay, we don't expect it to happen instantly).
392 enableRemoteAudio(gSecondConnection, false);
393 ensureSilence(gSecondConnection);
397 function callAndEnsureLocalAudioTrackMutingWorks(beLenient) {
398 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
399 setAllEventsOccuredHandler(function() {
400 setAllEventsOccuredHandler(reportTestSuccess);
402 // Call is up, now mute the local track of the sending side and ensure
403 // the receiving side stops receiving audio.
404 enableLocalAudio(gFirstConnection, false);
405 ensureSilence(gSecondConnection);
409 function callAndEnsureAudioTrackUnmutingWorks(beLenient) {
410 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
411 setAllEventsOccuredHandler(function() {
412 setAllEventsOccuredHandler(reportTestSuccess);
414 // Mute, wait a while, unmute, verify audio gets back up.
415 // (Also, ensure video muting doesn't affect audio).
416 enableRemoteAudio(gSecondConnection, false);
417 enableRemoteVideo(gSecondConnection, false);
419 setTimeout(function() {
420 enableRemoteAudio(gSecondConnection, true);
423 setTimeout(function() {
424 ensureAudioPlaying(gSecondConnection, beLenient);
429 function callAndEnsureLocalVideoMutingDoesntMuteAudio(beLenient) {
430 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
431 setAllEventsOccuredHandler(function() {
432 setAllEventsOccuredHandler(reportTestSuccess);
433 enableLocalVideo(gFirstConnection, false);
434 ensureAudioPlaying(gSecondConnection, beLenient);
438 function callAndEnsureRemoteVideoMutingDoesntMuteAudio(beLenient) {
439 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
440 setAllEventsOccuredHandler(function() {
441 setAllEventsOccuredHandler(reportTestSuccess);
442 enableRemoteVideo(gSecondConnection, false);
443 ensureAudioPlaying(gSecondConnection, beLenient);
447 function callAndEnsureVideoTrackMutingWorks() {
448 createConnections(null);
449 navigator.webkitGetUserMedia({audio: true, video: true},
450 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
453 detectVideoPlaying('remote-view-2', function() {
454 // Disable the receiver's remote media stream. Video should stop.
455 // (Also, ensure muting audio doesn't affect video).
456 enableRemoteVideo(gSecondConnection, false);
457 enableRemoteAudio(gSecondConnection, false);
459 detectVideoStopped('remote-view-2', function() {
460 // Video has stopped: unmute and succeed if it starts playing again.
461 enableRemoteVideo(gSecondConnection, true);
462 detectVideoPlaying('remote-view-2', eventOccured);
467 // Test call with a new Video MediaStream that has been created based on a
468 // stream generated by getUserMedia.
469 function callWithNewVideoMediaStream() {
470 createConnections(null);
471 navigator.webkitGetUserMedia({audio: true, video: true},
472 createNewVideoStreamAndAddToBothConnections, printGetUserMediaError);
473 waitForVideo('remote-view-1');
474 waitForVideo('remote-view-2');
477 // Test call with a new Video MediaStream that has been created based on a
478 // stream generated by getUserMedia. When Video is flowing, an audio track
479 // is added to the sent stream and the video track is removed. This
480 // is to test that adding and removing of remote tracks on an existing
481 // mediastream works.
482 function callWithNewVideoMediaStreamLaterSwitchToAudio() {
483 createConnections(null);
484 navigator.webkitGetUserMedia({audio: true, video: true},
485 createNewVideoStreamAndAddToBothConnections, printGetUserMediaError);
487 waitForVideo('remote-view-1');
488 waitForVideo('remote-view-2');
490 // Set an event handler for when video is playing.
491 setAllEventsOccuredHandler(function() {
492 // Add an audio track to the local stream and remove the video track and
493 // then renegotiate. But first - setup the expectations.
494 local_stream = gFirstConnection.getLocalStreams()[0];
496 remote_stream_1 = gFirstConnection.getRemoteStreams()[0];
497 // Add an expected event that onaddtrack will be called on the remote
498 // mediastream received on gFirstConnection when the audio track is
501 remote_stream_1.onaddtrack = function(){
502 assertEquals(remote_stream_1.getAudioTracks()[0].id,
503 local_stream.getAudioTracks()[0].id);
507 // Add an expectation that the received video track is removed from
510 remote_stream_1.onremovetrack = function() {
514 // Add an expected event that onaddtrack will be called on the remote
515 // mediastream received on gSecondConnection when the audio track is
517 remote_stream_2 = gSecondConnection.getRemoteStreams()[0];
519 remote_stream_2.onaddtrack = function() {
520 assertEquals(remote_stream_2.getAudioTracks()[0].id,
521 local_stream.getAudioTracks()[0].id);
525 // Add an expectation that the received video track is removed from
526 // gSecondConnection.
528 remote_stream_2.onremovetrack = function() {
531 // When all the above events have occurred- the test pass.
532 setAllEventsOccuredHandler(reportTestSuccess);
534 local_stream.addTrack(gLocalStream.getAudioTracks()[0]);
535 local_stream.removeTrack(local_stream.getVideoTracks()[0]);
540 // This function is used for setting up a test that:
541 // 1. Creates a data channel on |gFirstConnection| and sends data to
542 // |gSecondConnection|.
543 // 2. When data is received on |gSecondConnection| a message
544 // is sent to |gFirstConnection|.
545 // 3. When data is received on |gFirstConnection|, the data
546 // channel is closed. The test passes when the state transition completes.
547 function setupDataChannel(params) {
548 var sendDataString = "send some text on a data channel."
549 firstDataChannel = gFirstConnection.createDataChannel(
550 "sendDataChannel", params);
551 assertEquals('connecting', firstDataChannel.readyState);
553 // When |firstDataChannel| transition to open state, send a text string.
554 firstDataChannel.onopen = function() {
555 assertEquals('open', firstDataChannel.readyState);
556 firstDataChannel.send(sendDataString);
559 // When |firstDataChannel| receive a message, close the channel and
560 // initiate a new offer/answer exchange to complete the closure.
561 firstDataChannel.onmessage = function(event) {
562 assertEquals(event.data, sendDataString);
563 firstDataChannel.close();
567 // When |firstDataChannel| transition to closed state, the test pass.
569 firstDataChannel.onclose = function() {
570 assertEquals('closed', firstDataChannel.readyState);
574 // Event handler for when |gSecondConnection| receive a new dataChannel.
575 gSecondConnection.ondatachannel = function (event) {
576 var secondDataChannel = event.channel;
578 // When |secondDataChannel| receive a message, send a message back.
579 secondDataChannel.onmessage = function(event) {
580 assertEquals(event.data, sendDataString);
581 console.log("gSecondConnection received data");
582 assertEquals('open', secondDataChannel.readyState);
583 secondDataChannel.send(sendDataString);
588 // SCTP data channel setup is slightly different then RTP based
589 // channels. Due to a bug in libjingle, we can't send data immediately
590 // after channel becomes open. So for that reason in SCTP,
591 // we are sending data from second channel, when ondatachannel event is
592 // received. So data flow happens 2 -> 1 -> 2.
593 function setupSctpDataChannel(params) {
594 var sendDataString = "send some text on a data channel."
595 firstDataChannel = gFirstConnection.createDataChannel(
596 "sendDataChannel", params);
597 assertEquals('connecting', firstDataChannel.readyState);
599 // When |firstDataChannel| transition to open state, send a text string.
600 firstDataChannel.onopen = function() {
601 assertEquals('open', firstDataChannel.readyState);
604 // When |firstDataChannel| receive a message, send message back.
605 // initiate a new offer/answer exchange to complete the closure.
606 firstDataChannel.onmessage = function(event) {
607 assertEquals('open', firstDataChannel.readyState);
608 assertEquals(event.data, sendDataString);
609 firstDataChannel.send(sendDataString);
612 // Event handler for when |gSecondConnection| receive a new dataChannel.
613 gSecondConnection.ondatachannel = function (event) {
614 var secondDataChannel = event.channel;
615 secondDataChannel.onopen = function() {
616 secondDataChannel.send(sendDataString);
619 // When |secondDataChannel| receive a message, close the channel and
620 // initiate a new offer/answer exchange to complete the closure.
621 secondDataChannel.onmessage = function(event) {
622 assertEquals(event.data, sendDataString);
623 assertEquals('open', secondDataChannel.readyState);
624 secondDataChannel.close();
628 // When |secondDataChannel| transition to closed state, the test pass.
630 secondDataChannel.onclose = function() {
631 assertEquals('closed', secondDataChannel.readyState);
637 // Test call with a stream that has been created by getUserMedia, clone
638 // the stream to a cloned stream, send them via the same peer connection.
639 function addTwoMediaStreamsToOneConnection() {
640 createConnections(null);
641 navigator.webkitGetUserMedia({audio: true, video: true},
642 CloneStreamAndAddTwoStreamstoOneConnection, printGetUserMediaError);
645 function onToneChange(tone) {
646 gSentTones += tone.tone;
649 function createConnections(constraints) {
650 gFirstConnection = createConnection(constraints, 'remote-view-1');
651 assertEquals('stable', gFirstConnection.signalingState);
653 gSecondConnection = createConnection(constraints, 'remote-view-2');
654 assertEquals('stable', gSecondConnection.signalingState);
657 function createConnection(constraints, remoteView) {
658 var pc = new webkitRTCPeerConnection(null, constraints);
659 pc.onaddstream = function(event) {
660 onRemoteStream(event, remoteView);
665 function displayAndRemember(localStream) {
666 var localStreamUrl = URL.createObjectURL(localStream);
667 $('local-view').src = localStreamUrl;
669 gLocalStream = localStream;
672 // Called if getUserMedia fails.
673 function printGetUserMediaError(error) {
674 var message = 'getUserMedia request unexpectedly failed:';
675 if (error.constraintName)
676 message += ' could not satisfy constraint ' + error.constraintName;
678 message += ' devices not working/user denied access.';
682 // Called if getUserMedia succeeds and we want to send from both connections.
683 function addStreamToBothConnectionsAndNegotiate(localStream) {
684 displayAndRemember(localStream);
685 gFirstConnection.addStream(localStream);
686 gSecondConnection.addStream(localStream);
690 // Called if getUserMedia succeeds when we want to send from one connection.
691 function addStreamToTheFirstConnectionAndNegotiate(localStream) {
692 displayAndRemember(localStream);
693 gFirstConnection.addStream(localStream);
697 function verifyHasOneAudioAndVideoTrack(stream) {
698 assertEquals(1, stream.getAudioTracks().length);
699 assertEquals(1, stream.getVideoTracks().length);
702 // Called if getUserMedia succeeds, then clone the stream, send two streams
703 // from one peer connection.
704 function CloneStreamAndAddTwoStreamstoOneConnection(localStream) {
705 displayAndRemember(localStream);
707 var clonedStream = null;
708 if (typeof localStream.clone === "function") {
709 clonedStream = localStream.clone();
711 clonedStream = new webkitMediaStream(localStream);
714 gFirstConnection.addStream(localStream);
715 gFirstConnection.addStream(clonedStream);
717 // Verify the local streams are correct.
718 assertEquals(2, gFirstConnection.getLocalStreams().length);
719 verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[0]);
720 verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[1]);
722 // The remote side should receive two streams. After that, verify the
723 // remote side has the correct number of streams and tracks.
726 gSecondConnection.onaddstream = function(event) {
729 setAllEventsOccuredHandler(function() {
730 // Negotiation complete, verify remote streams on the receiving side.
731 assertEquals(2, gSecondConnection.getRemoteStreams().length);
732 verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[0]);
733 verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[1]);
741 // Called if getUserMedia succeeds when we want to send a modified
742 // MediaStream. A new MediaStream is created and the video track from
743 // |localStream| is added.
744 function createNewVideoStreamAndAddToBothConnections(localStream) {
745 displayAndRemember(localStream);
746 var new_stream = new webkitMediaStream();
747 new_stream.addTrack(localStream.getVideoTracks()[0]);
748 gFirstConnection.addStream(new_stream);
749 gSecondConnection.addStream(new_stream);
753 function negotiate() {
754 negotiateBetween(gFirstConnection, gSecondConnection);
757 function negotiateBetween(caller, callee) {
758 console.log("Negotiating call...");
759 // Not stable = negotiation is ongoing. The behavior of re-negotiating while
760 // a negotiation is ongoing is more or less undefined, so avoid this.
761 if (caller.signalingState != 'stable' || callee.signalingState != 'stable')
762 throw 'You can only negotiate when the connection is stable!';
764 connectOnIceCandidate(caller, callee);
768 onOfferCreated(offer, caller, callee);
772 function onOfferCreated(offer, caller, callee) {
773 offer.sdp = maybeForceIsac16K(transformSdp(offer.sdp));
774 caller.setLocalDescription(offer, function() {
775 assertEquals('have-local-offer', caller.signalingState);
776 receiveOffer(offer.sdp, caller, callee);
777 }, onLocalDescriptionError);
780 function receiveOffer(offerSdp, caller, callee) {
781 console.log("Receiving offer...");
782 offerSdp = transformRemoteSdp(offerSdp);
784 var parsedOffer = new RTCSessionDescription({ type: 'offer',
786 callee.setRemoteDescription(parsedOffer,
788 assertEquals('have-remote-offer',
789 callee.signalingState);
792 onAnswerCreated(answer, caller, callee);
795 onRemoteDescriptionError);
798 function removeMsid(offerSdp) {
799 offerSdp = offerSdp.replace(/a=msid-semantic.*\r\n/g, '');
800 offerSdp = offerSdp.replace('a=mid:audio\r\n', '');
801 offerSdp = offerSdp.replace('a=mid:video\r\n', '');
802 offerSdp = offerSdp.replace(/a=ssrc.*\r\n/g, '');
806 function removeVideoCodec(offerSdp) {
807 offerSdp = offerSdp.replace('a=rtpmap:100 VP8/90000\r\n',
808 'a=rtpmap:100 XVP8/90000\r\n');
812 function removeCrypto(offerSdp) {
813 offerSdp = offerSdp.replace(/a=crypto.*\r\n/g, 'a=Xcrypto\r\n');
814 offerSdp = offerSdp.replace(/a=fingerprint.*\r\n/g, '');
818 function addBandwithControl(offerSdp) {
819 offerSdp = offerSdp.replace('a=mid:audio\r\n', 'a=mid:audio\r\n'+
821 offerSdp = offerSdp.replace('a=mid:video\r\n', 'a=mid:video\r\n'+
826 function removeBundle(sdp) {
827 return sdp.replace(/a=group:BUNDLE .*\r\n/g, '');
830 function useGice(sdp) {
831 sdp = sdp.replace(/t=.*\r\n/g, function(subString) {
832 return subString + 'a=ice-options:google-ice\r\n';
837 function useExternalSdes(sdp) {
838 // Remove current crypto specification.
839 sdp = sdp.replace(/a=crypto.*\r\n/g, '');
840 sdp = sdp.replace(/a=fingerprint.*\r\n/g, '');
841 // Add external crypto. This is not compatible with |removeMsid|.
842 sdp = sdp.replace(/a=mid:(\w+)\r\n/g, function(subString, group) {
843 return subString + EXTERNAL_SDES_LINES[group] + '\r\n';
848 function onAnswerCreated(answer, caller, callee) {
849 answer.sdp = maybeForceIsac16K(transformSdp(answer.sdp));
850 callee.setLocalDescription(answer,
852 assertEquals('stable', callee.signalingState);
854 onLocalDescriptionError);
855 receiveAnswer(answer.sdp, caller);
858 function receiveAnswer(answerSdp, caller) {
859 console.log("Receiving answer...");
860 answerSdp = transformRemoteSdp(answerSdp);
861 var parsedAnswer = new RTCSessionDescription({ type: 'answer',
863 caller.setRemoteDescription(parsedAnswer,
865 assertEquals('stable', caller.signalingState);
867 onRemoteDescriptionError);
870 function connectOnIceCandidate(caller, callee) {
871 caller.onicecandidate = function(event) { onIceCandidate(event, callee); }
872 callee.onicecandidate = function(event) { onIceCandidate(event, caller); }
875 function onIceCandidate(event, target) {
876 if (event.candidate) {
877 var candidate = new RTCIceCandidate(event.candidate);
878 target.addIceCandidate(candidate);
882 function onRemoteStream(e, target) {
883 console.log("Receiving remote stream...");
884 if (gTestWithoutMsid && e.stream.id != "default") {
885 failTest('a default remote stream was expected but instead ' +
886 e.stream.id + ' was received.');
888 gRemoteStreams[target] = e.stream;
889 var remoteStreamUrl = URL.createObjectURL(e.stream);
890 var remoteVideo = $(target);
891 remoteVideo.src = remoteStreamUrl;
899 <td><video width="320" height="240" id="local-view" style="display:none"
900 autoplay muted></video></td>
901 <td><video width="320" height="240" id="remote-view-1"
902 style="display:none" autoplay></video></td>
903 <td><video width="320" height="240" id="remote-view-2"
904 style="display:none" autoplay></video></td>
905 <td><video width="320" height="240" id="remote-view-3"
906 style="display:none" autoplay></video></td>
907 <td><video width="320" height="240" id="remote-view-4"
908 style="display:none" autoplay></video></td>
909 <!-- Canvases are named after their corresponding video elements. -->
910 <td><canvas width="320" height="240" id="remote-view-1-canvas"
911 style="display:none"></canvas></td>
912 <td><canvas width="320" height="240" id="remote-view-2-canvas"
913 style="display:none"></canvas></td>
914 <td><canvas width="320" height="240" id="remote-view-3-canvas"
915 style="display:none"></canvas></td>
916 <td><canvas width="320" height="240" id="remote-view-4-canvas"
917 style="display:none"></canvas></td>