Fixing misnamed variables, re-enabling CallAndModifyStream test.
[chromium-blink-merge.git] / content / test / data / media / peerconnection-call.html
blob345d0ea45eb713b45716a2405d69496afc6bbc2b
1 <html>
2 <head>
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">
6 $ = function(id) {
7 return document.getElementById(id);
8 };
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;
20 var gSentTones = '';
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
32 // this.
33 var maybeForceIsac16K = function(sdp) { return sdp; };
34 function forceIsac16KInSdp() {
35 maybeForceIsac16K = function(sdp) {
36 if (sdp.search('m=audio') == -1)
37 return sdp;
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.');
45 return sdp;
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 a call with an audio and video track (must request
63 // video in this call since we expect video to be playing).
64 function call(constraints) {
65 createConnections(null);
66 navigator.webkitGetUserMedia(constraints,
67 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
68 waitForVideo('remote-view-1');
69 waitForVideo('remote-view-2');
72 // Test that we can setup a call with a video track and that the remote peer
73 // receives black frames if the local video track is disabled.
74 function callAndDisableLocalVideo(constraints) {
75 createConnections(null);
76 navigator.webkitGetUserMedia(constraints,
77 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
78 detectVideoPlaying('remote-view-1',
79 function () {
80 assertEquals(gLocalStream.getVideoTracks().length, 1);
81 gLocalStream.getVideoTracks()[0].enabled = false;
82 waitForBlackVideo('remote-view-1');
83 });
86 // Test that we can setup call with an audio and video track and check that
87 // the video resolution is as expected.
88 function callAndExpectResolution(constraints,
89 expected_width,
90 expected_height) {
91 createConnections(null);
92 navigator.webkitGetUserMedia(constraints,
93 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
94 waitForVideoWithResolution('remote-view-1',
95 expected_width,
96 expected_height);
97 waitForVideoWithResolution('remote-view-2',
98 expected_width,
99 expected_height);
103 // First calls without streams on any connections, and then adds a stream
104 // to peer connection 1 which gets sent to peer connection 2. We must wait
105 // for the first negotiation to complete before starting the second one, which
106 // is why we wait until the connection is stable before re-negotiating.
107 function callEmptyThenAddOneStreamAndRenegotiate(constraints) {
108 createConnections(null);
109 negotiate();
110 waitForConnectionToStabilize(gFirstConnection, function() {
111 navigator.webkitGetUserMedia(constraints,
112 addStreamToTheFirstConnectionAndNegotiate, printGetUserMediaError);
113 // Only the first connection is sending here.
114 waitForVideo('remote-view-2');
118 // The second set of constraints should request video (e.g. video:true) since
119 // we expect video to be playing after the second renegotiation.
120 function callAndRenegotiateToVideo(constraints, renegotiationConstraints) {
121 createConnections(null);
122 navigator.webkitGetUserMedia(constraints,
123 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
125 waitForConnectionToStabilize(gFirstConnection, function() {
126 gFirstConnection.removeStream(gLocalStream);
127 gSecondConnection.removeStream(gLocalStream);
129 navigator.webkitGetUserMedia(renegotiationConstraints,
130 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
131 waitForVideo('remote-view-1');
132 waitForVideo('remote-view-2');
136 // The second set of constraints should request audio (e.g. audio:true) since
137 // we expect audio to be playing after the second renegotiation.
138 function callAndRenegotiateToAudio(beLenient, constraints,
139 renegotiationConstraints) {
140 createConnections(null);
141 navigator.webkitGetUserMedia(constraints,
142 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
144 waitForConnectionToStabilize(gFirstConnection, function() {
145 gFirstConnection.removeStream(gLocalStream);
146 gSecondConnection.removeStream(gLocalStream);
148 navigator.webkitGetUserMedia(renegotiationConstraints,
149 addStreamToTheFirstConnectionAndNegotiate, printGetUserMediaError);
151 var onCallEstablished = function() {
152 ensureAudioPlaying(gSecondConnection, beLenient);
155 waitForConnectionToStabilize(gFirstConnection, onCallEstablished);
159 // First makes a call between pc1 and pc2, and then makes a call between pc3
160 // and pc4. The stream sent from pc3 to pc4 is the stream received on pc1.
161 // The stream sent from pc4 to pc3 is cloned from the stream received on pc2
162 // to test that cloning of remote video tracks works as intended.
163 function callAndForwardRemoteStream(constraints) {
164 createConnections(null);
165 navigator.webkitGetUserMedia(constraints,
166 addStreamToBothConnectionsAndNegotiate,
167 printGetUserMediaError);
168 var gotRemoteStream1 = false;
169 var gotRemoteStream2 = false;
171 var onRemoteStream1 = function() {
172 gotRemoteStream1 = true;
173 maybeCallEstablished();
176 var onRemoteStream2 = function() {
177 gotRemoteStream2 = true;
178 maybeCallEstablished();
181 var maybeCallEstablished = function() {
182 if (gotRemoteStream1 && gotRemoteStream2) {
183 onCallEstablished();
187 var onCallEstablished = function() {
188 thirdConnection = createConnection(null, 'remote-view-3');
189 thirdConnection.addStream(gRemoteStreams['remote-view-1']);
191 fourthConnection = createConnection(null, 'remote-view-4');
192 fourthConnection.addStream(gRemoteStreams['remote-view-2'].clone());
194 negotiateBetween(thirdConnection, fourthConnection);
196 waitForVideo('remote-view-3');
197 waitForVideo('remote-view-4');
200 // Do the forwarding after we have received video.
201 detectVideoPlaying('remote-view-1', onRemoteStream1);
202 detectVideoPlaying('remote-view-2', onRemoteStream2);
205 // First makes a call between pc1 and pc2, and then construct a new media
206 // stream using the remote audio and video tracks, connect the new media
207 // stream to a video element. These operations should not crash Chrome.
208 function ConnectChromiumSinkToRemoteAudioTrack() {
209 createConnections(null);
210 navigator.webkitGetUserMedia({audio: true, video: true},
211 addStreamToBothConnectionsAndNegotiate,
212 printGetUserMediaError);
214 detectVideoPlaying('remote-view-2', function() {
215 // Construct a new media stream with remote tracks.
216 var newStream = new webkitMediaStream();
217 newStream.addTrack(
218 gSecondConnection.getRemoteStreams()[0].getAudioTracks()[0]);
219 newStream.addTrack(
220 gSecondConnection.getRemoteStreams()[0].getVideoTracks()[0]);
221 var videoElement = document.createElement('video');
223 // No crash for this operation.
224 videoElement.src = URL.createObjectURL(newStream);
225 waitForVideo('remote-view-2');
229 // Test that we can setup call with an audio and video track and
230 // simulate that the remote peer don't support MSID.
231 function callWithoutMsidAndBundle() {
232 createConnections(null);
233 transformSdp = removeBundle;
234 transformRemoteSdp = removeMsid;
235 gTestWithoutMsid = true;
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't setup a call with an unsupported video codec
243 function negotiateUnsupportedVideoCodec() {
244 createConnections(null);
245 transformSdp = removeVideoCodec;
247 onLocalDescriptionError = function(error) {
248 var expectedMsg = 'Failed to set local offer sdp:' +
249 ' Session error code: ERROR_CONTENT. Session error description:' +
250 ' Failed to set video receive codecs..';
251 assertEquals(expectedMsg, error);
252 reportTestSuccess();
254 navigator.webkitGetUserMedia({audio: true, video: true},
255 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
258 // Test that we can't setup a call if one peer does not support encryption
259 function negotiateNonCryptoCall() {
260 createConnections(null);
261 transformSdp = removeCrypto;
262 onLocalDescriptionError = function(error) {
263 var expectedMsg = 'Failed to set local offer sdp:' +
264 ' Called with SDP without DTLS fingerprint.';
266 assertEquals(expectedMsg, error);
267 reportTestSuccess();
269 navigator.webkitGetUserMedia({audio: true, video: true},
270 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
273 // Test that we can negotiate a call with an SDP offer that includes a
274 // b=AS:XX line to control audio and video bandwidth
275 function negotiateOfferWithBLine() {
276 createConnections(null);
277 transformSdp = addBandwithControl;
278 navigator.webkitGetUserMedia({audio: true, video: true},
279 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
280 waitForVideo('remote-view-1');
281 waitForVideo('remote-view-2');
284 // Test that we can setup call with legacy settings.
285 function callWithLegacySdp() {
286 transformSdp = function(sdp) {
287 return removeBundle(useGice(useExternalSdes(sdp)));
289 createConnections({
290 'mandatory': {'RtpDataChannels': true, 'DtlsSrtpKeyAgreement': false}
292 setupDataChannel({reliable: false});
293 navigator.webkitGetUserMedia({audio: true, video: true},
294 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
295 waitForVideo('remote-view-1');
296 waitForVideo('remote-view-2');
299 // Test only a data channel.
300 function callWithDataOnly() {
301 createConnections({optional:[{RtpDataChannels: true}]});
302 setupDataChannel({reliable: false});
303 negotiate();
306 function callWithSctpDataOnly() {
307 createConnections({optional: [{DtlsSrtpKeyAgreement: true}]});
308 setupSctpDataChannel({reliable: true});
309 negotiate();
312 // Test call with audio, video and a data channel.
313 function callWithDataAndMedia() {
314 createConnections({optional:[{RtpDataChannels: true}]});
315 setupDataChannel({reliable: false});
316 navigator.webkitGetUserMedia({audio: true, video: true},
317 addStreamToBothConnectionsAndNegotiate,
318 printGetUserMediaError);
319 waitForVideo('remote-view-1');
320 waitForVideo('remote-view-2');
323 function callWithSctpDataAndMedia() {
324 createConnections({optional: [{DtlsSrtpKeyAgreement: true}]});
325 setupSctpDataChannel({reliable: true});
326 navigator.webkitGetUserMedia({audio: true, video: true},
327 addStreamToBothConnectionsAndNegotiate,
328 printGetUserMediaError);
329 waitForVideo('remote-view-1');
330 waitForVideo('remote-view-2');
333 // Test call with a data channel and later add audio and video.
334 function callWithDataAndLaterAddMedia() {
335 createConnections({optional:[{RtpDataChannels: true}]});
336 setupDataChannel({reliable: false});
337 negotiate();
339 // Set an event handler for when the data channel has been closed.
340 setAllEventsOccuredHandler(function() {
341 // When the video is flowing the test is done.
342 setAllEventsOccuredHandler(reportTestSuccess);
343 navigator.webkitGetUserMedia({audio: true, video: true},
344 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
345 waitForVideo('remote-view-1');
346 waitForVideo('remote-view-2');
350 // Test that we can setup call and send DTMF.
351 function callAndSendDtmf(tones) {
352 createConnections(null);
353 navigator.webkitGetUserMedia({audio: true, video: true},
354 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
355 var onCallEstablished = function() {
356 // Send DTMF tones.
357 var localAudioTrack = gLocalStream.getAudioTracks()[0];
358 var dtmfSender = gFirstConnection.createDTMFSender(localAudioTrack);
359 dtmfSender.ontonechange = onToneChange;
360 dtmfSender.insertDTMF(tones);
361 // Wait for the DTMF tones callback.
362 addExpectedEvent();
363 var waitDtmf = setInterval(function() {
364 if (gSentTones == tones) {
365 clearInterval(waitDtmf);
366 eventOccured();
368 }, 100);
371 // Do the DTMF test after we have received video.
372 detectVideoPlaying('remote-view-2', onCallEstablished);
375 function testCreateOfferOptions() {
376 createConnections(null);
377 var offerOptions = {
378 'offerToReceiveAudio': false,
379 'offerToReceiveVideo': true
382 gFirstConnection.createOffer(
383 function(offer) {
384 assertEquals(-1, offer.sdp.search('m=audio'));
385 assertNotEquals(-1, offer.sdp.search('m=video'));
387 reportTestSuccess();
389 function(error) { failTest(error); },
390 offerOptions);
393 function callAndEnsureAudioIsPlaying(beLenient, constraints) {
394 createConnections(null);
396 // Add the local stream to gFirstConnection to play one-way audio.
397 navigator.webkitGetUserMedia(constraints,
398 addStreamToTheFirstConnectionAndNegotiate, printGetUserMediaError);
400 var onCallEstablished = function() {
401 ensureAudioPlaying(gSecondConnection, beLenient);
404 waitForConnectionToStabilize(gFirstConnection, onCallEstablished);
407 function enableRemoteVideo(peerConnection, enabled) {
408 remoteStream = peerConnection.getRemoteStreams()[0];
409 remoteStream.getVideoTracks()[0].enabled = enabled;
412 function enableRemoteAudio(peerConnection, enabled) {
413 remoteStream = peerConnection.getRemoteStreams()[0];
414 remoteStream.getAudioTracks()[0].enabled = enabled;
417 function enableLocalVideo(peerConnection, enabled) {
418 localStream = peerConnection.getLocalStreams()[0];
419 localStream.getVideoTracks()[0].enabled = enabled;
422 function enableLocalAudio(peerConnection, enabled) {
423 localStream = peerConnection.getLocalStreams()[0];
424 localStream.getAudioTracks()[0].enabled = enabled;
427 function callAndEnsureRemoteAudioTrackMutingWorks(beLenient) {
428 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
429 setAllEventsOccuredHandler(function() {
430 setAllEventsOccuredHandler(reportTestSuccess);
432 // Call is up, now mute the remote track and check we stop playing out
433 // audio (after a small delay, we don't expect it to happen instantly).
434 enableRemoteAudio(gSecondConnection, false);
435 ensureSilence(gSecondConnection);
439 function callAndEnsureLocalAudioTrackMutingWorks(beLenient) {
440 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
441 setAllEventsOccuredHandler(function() {
442 setAllEventsOccuredHandler(reportTestSuccess);
444 // Call is up, now mute the local track of the sending side and ensure
445 // the receiving side stops receiving audio.
446 enableLocalAudio(gFirstConnection, false);
447 ensureSilence(gSecondConnection);
451 function callAndEnsureAudioTrackUnmutingWorks(beLenient) {
452 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
453 setAllEventsOccuredHandler(function() {
454 setAllEventsOccuredHandler(reportTestSuccess);
456 // Mute, wait a while, unmute, verify audio gets back up.
457 // (Also, ensure video muting doesn't affect audio).
458 enableRemoteAudio(gSecondConnection, false);
459 enableRemoteVideo(gSecondConnection, false);
461 setTimeout(function() {
462 enableRemoteAudio(gSecondConnection, true);
463 }, 500);
465 setTimeout(function() {
466 ensureAudioPlaying(gSecondConnection, beLenient);
467 }, 1500);
471 function callAndEnsureLocalVideoMutingDoesntMuteAudio(beLenient) {
472 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
473 setAllEventsOccuredHandler(function() {
474 setAllEventsOccuredHandler(reportTestSuccess);
475 enableLocalVideo(gFirstConnection, false);
476 ensureAudioPlaying(gSecondConnection, beLenient);
480 function callAndEnsureRemoteVideoMutingDoesntMuteAudio(beLenient) {
481 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
482 setAllEventsOccuredHandler(function() {
483 setAllEventsOccuredHandler(reportTestSuccess);
484 enableRemoteVideo(gSecondConnection, false);
485 ensureAudioPlaying(gSecondConnection, beLenient);
489 function callAndEnsureVideoTrackMutingWorks() {
490 createConnections(null);
491 navigator.webkitGetUserMedia({audio: true, video: true},
492 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
494 addExpectedEvent();
495 detectVideoPlaying('remote-view-2', function() {
496 // Disable the receiver's remote media stream. Video should stop.
497 // (Also, ensure muting audio doesn't affect video).
498 enableRemoteVideo(gSecondConnection, false);
499 enableRemoteAudio(gSecondConnection, false);
501 detectVideoStopped('remote-view-2', function() {
502 // Video has stopped: unmute and succeed if it starts playing again.
503 enableRemoteVideo(gSecondConnection, true);
504 detectVideoPlaying('remote-view-2', eventOccured);
509 // Test call with a new Video MediaStream that has been created based on a
510 // stream generated by getUserMedia.
511 function callWithNewVideoMediaStream() {
512 createConnections(null);
513 navigator.webkitGetUserMedia({audio: true, video: true},
514 createNewVideoStreamAndAddToBothConnections, printGetUserMediaError);
515 waitForVideo('remote-view-1');
516 waitForVideo('remote-view-2');
519 // Test call with a new Video MediaStream that has been created based on a
520 // stream generated by getUserMedia. When Video is flowing, an audio track
521 // is added to the sent stream and the video track is removed. This
522 // is to test that adding and removing of remote tracks on an existing
523 // mediastream works.
524 function callWithNewVideoMediaStreamLaterSwitchToAudio() {
525 createConnections(null);
526 navigator.webkitGetUserMedia({audio: true, video: true},
527 createNewVideoStreamAndAddToBothConnections, printGetUserMediaError);
529 waitForVideo('remote-view-1');
530 waitForVideo('remote-view-2');
532 // Set an event handler for when video is playing.
533 setAllEventsOccuredHandler(function() {
534 // Add an audio track to the local stream and remove the video track and
535 // then renegotiate. But first - setup the expectations.
536 var localStream = gFirstConnection.getLocalStreams()[0];
537 var remoteStream1 = gFirstConnection.getRemoteStreams()[0];
539 // Add an expected event that onaddtrack will be called on the remote
540 // mediastream received on gFirstConnection when the audio track is
541 // received.
542 addExpectedEvent();
543 remoteStream1.onaddtrack = function(){
544 assertEquals(remoteStream1.getAudioTracks()[0].id,
545 localStream.getAudioTracks()[0].id);
546 eventOccured();
549 // Add an expectation that the received video track is removed from
550 // gFirstConnection.
551 addExpectedEvent();
552 remoteStream1.onremovetrack = function() {
553 eventOccured();
556 // Add an expected event that onaddtrack will be called on the remote
557 // mediastream received on gSecondConnection when the audio track is
558 // received.
559 remoteStream2 = gSecondConnection.getRemoteStreams()[0];
560 addExpectedEvent();
561 remoteStream2.onaddtrack = function() {
562 assertEquals(remoteStream2.getAudioTracks()[0].id,
563 localStream.getAudioTracks()[0].id);
564 eventOccured();
567 // Add an expectation that the received video track is removed from
568 // gSecondConnection.
569 addExpectedEvent();
570 remoteStream2.onremovetrack = function() {
571 eventOccured();
573 // When all the above events have occurred- the test pass.
574 setAllEventsOccuredHandler(reportTestSuccess);
576 localStream.addTrack(gLocalStream.getAudioTracks()[0]);
577 localStream.removeTrack(localStream.getVideoTracks()[0]);
578 negotiate();
582 // This function is used for setting up a test that:
583 // 1. Creates a data channel on |gFirstConnection| and sends data to
584 // |gSecondConnection|.
585 // 2. When data is received on |gSecondConnection| a message
586 // is sent to |gFirstConnection|.
587 // 3. When data is received on |gFirstConnection|, the data
588 // channel is closed. The test passes when the state transition completes.
589 function setupDataChannel(params) {
590 var sendDataString = "send some text on a data channel."
591 firstDataChannel = gFirstConnection.createDataChannel(
592 "sendDataChannel", params);
593 assertEquals('connecting', firstDataChannel.readyState);
595 // When |firstDataChannel| transition to open state, send a text string.
596 firstDataChannel.onopen = function() {
597 assertEquals('open', firstDataChannel.readyState);
598 firstDataChannel.send(sendDataString);
601 // When |firstDataChannel| receive a message, close the channel and
602 // initiate a new offer/answer exchange to complete the closure.
603 firstDataChannel.onmessage = function(event) {
604 assertEquals(event.data, sendDataString);
605 firstDataChannel.close();
606 negotiate();
609 // When |firstDataChannel| transition to closed state, the test pass.
610 addExpectedEvent();
611 firstDataChannel.onclose = function() {
612 assertEquals('closed', firstDataChannel.readyState);
613 eventOccured();
616 // Event handler for when |gSecondConnection| receive a new dataChannel.
617 gSecondConnection.ondatachannel = function (event) {
618 var secondDataChannel = event.channel;
620 // When |secondDataChannel| receive a message, send a message back.
621 secondDataChannel.onmessage = function(event) {
622 assertEquals(event.data, sendDataString);
623 console.log("gSecondConnection received data");
624 assertEquals('open', secondDataChannel.readyState);
625 secondDataChannel.send(sendDataString);
630 // SCTP data channel setup is slightly different then RTP based
631 // channels. Due to a bug in libjingle, we can't send data immediately
632 // after channel becomes open. So for that reason in SCTP,
633 // we are sending data from second channel, when ondatachannel event is
634 // received. So data flow happens 2 -> 1 -> 2.
635 function setupSctpDataChannel(params) {
636 var sendDataString = "send some text on a data channel."
637 firstDataChannel = gFirstConnection.createDataChannel(
638 "sendDataChannel", params);
639 assertEquals('connecting', firstDataChannel.readyState);
641 // When |firstDataChannel| transition to open state, send a text string.
642 firstDataChannel.onopen = function() {
643 assertEquals('open', firstDataChannel.readyState);
646 // When |firstDataChannel| receive a message, send message back.
647 // initiate a new offer/answer exchange to complete the closure.
648 firstDataChannel.onmessage = function(event) {
649 assertEquals('open', firstDataChannel.readyState);
650 assertEquals(event.data, sendDataString);
651 firstDataChannel.send(sendDataString);
654 // Event handler for when |gSecondConnection| receive a new dataChannel.
655 gSecondConnection.ondatachannel = function (event) {
656 var secondDataChannel = event.channel;
657 secondDataChannel.onopen = function() {
658 secondDataChannel.send(sendDataString);
661 // When |secondDataChannel| receive a message, close the channel and
662 // initiate a new offer/answer exchange to complete the closure.
663 secondDataChannel.onmessage = function(event) {
664 assertEquals(event.data, sendDataString);
665 assertEquals('open', secondDataChannel.readyState);
666 secondDataChannel.close();
667 negotiate();
670 // When |secondDataChannel| transition to closed state, the test pass.
671 addExpectedEvent();
672 secondDataChannel.onclose = function() {
673 assertEquals('closed', secondDataChannel.readyState);
674 eventOccured();
679 // Test call with a stream that has been created by getUserMedia, clone
680 // the stream to a cloned stream, send them via the same peer connection.
681 function addTwoMediaStreamsToOneConnection() {
682 createConnections(null);
683 navigator.webkitGetUserMedia({audio: true, video: true},
684 cloneStreamAndAddTwoStreamsToOneConnection, printGetUserMediaError);
687 function onToneChange(tone) {
688 gSentTones += tone.tone;
691 function createConnections(constraints) {
692 gFirstConnection = createConnection(constraints, 'remote-view-1');
693 assertEquals('stable', gFirstConnection.signalingState);
695 gSecondConnection = createConnection(constraints, 'remote-view-2');
696 assertEquals('stable', gSecondConnection.signalingState);
699 function createConnection(constraints, remoteView) {
700 var pc = new webkitRTCPeerConnection(null, constraints);
701 pc.onaddstream = function(event) {
702 onRemoteStream(event, remoteView);
704 return pc;
707 function displayAndRemember(localStream) {
708 var localStreamUrl = URL.createObjectURL(localStream);
709 $('local-view').src = localStreamUrl;
711 gLocalStream = localStream;
714 // Called if getUserMedia fails.
715 function printGetUserMediaError(error) {
716 var message = 'getUserMedia request unexpectedly failed:';
717 if (error.constraintName)
718 message += ' could not satisfy constraint ' + error.constraintName;
719 else
720 message += ' devices not working/user denied access.';
721 failTest(message);
724 // Called if getUserMedia succeeds and we want to send from both connections.
725 function addStreamToBothConnectionsAndNegotiate(localStream) {
726 displayAndRemember(localStream);
727 gFirstConnection.addStream(localStream);
728 gSecondConnection.addStream(localStream);
729 negotiate();
732 // Called if getUserMedia succeeds when we want to send from one connection.
733 function addStreamToTheFirstConnectionAndNegotiate(localStream) {
734 displayAndRemember(localStream);
735 gFirstConnection.addStream(localStream);
736 negotiate();
739 function verifyHasOneAudioAndVideoTrack(stream) {
740 assertEquals(1, stream.getAudioTracks().length);
741 assertEquals(1, stream.getVideoTracks().length);
744 // Called if getUserMedia succeeds, then clone the stream, send two streams
745 // from one peer connection.
746 function cloneStreamAndAddTwoStreamsToOneConnection(localStream) {
747 displayAndRemember(localStream);
749 var clonedStream = null;
750 if (typeof localStream.clone === "function") {
751 clonedStream = localStream.clone();
752 } else {
753 clonedStream = new webkitMediaStream(localStream);
756 gFirstConnection.addStream(localStream);
757 gFirstConnection.addStream(clonedStream);
759 // Verify the local streams are correct.
760 assertEquals(2, gFirstConnection.getLocalStreams().length);
761 verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[0]);
762 verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[1]);
764 // The remote side should receive two streams. After that, verify the
765 // remote side has the correct number of streams and tracks.
766 addExpectedEvent();
767 addExpectedEvent();
768 gSecondConnection.onaddstream = function(event) {
769 eventOccured();
771 setAllEventsOccuredHandler(function() {
772 // Negotiation complete, verify remote streams on the receiving side.
773 assertEquals(2, gSecondConnection.getRemoteStreams().length);
774 verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[0]);
775 verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[1]);
777 reportTestSuccess();
780 negotiate();
783 // A new MediaStream is created with video track from |localStream| and is
784 // added to both peer connections.
785 function createNewVideoStreamAndAddToBothConnections(localStream) {
786 displayAndRemember(localStream);
787 var newStream = new webkitMediaStream();
788 newStream.addTrack(localStream.getVideoTracks()[0]);
789 gFirstConnection.addStream(newStream);
790 gSecondConnection.addStream(newStream);
791 negotiate();
794 function negotiate() {
795 negotiateBetween(gFirstConnection, gSecondConnection);
798 function negotiateBetween(caller, callee) {
799 console.log("Negotiating call...");
800 // Not stable = negotiation is ongoing. The behavior of re-negotiating while
801 // a negotiation is ongoing is more or less undefined, so avoid this.
802 if (caller.signalingState != 'stable' || callee.signalingState != 'stable')
803 throw 'You can only negotiate when the connection is stable!';
805 connectOnIceCandidate(caller, callee);
807 caller.createOffer(
808 function (offer) {
809 onOfferCreated(offer, caller, callee);
813 function onOfferCreated(offer, caller, callee) {
814 offer.sdp = maybeForceIsac16K(transformSdp(offer.sdp));
815 caller.setLocalDescription(offer, function() {
816 assertEquals('have-local-offer', caller.signalingState);
817 receiveOffer(offer.sdp, caller, callee);
818 }, onLocalDescriptionError);
821 function receiveOffer(offerSdp, caller, callee) {
822 console.log("Receiving offer...");
823 offerSdp = transformRemoteSdp(offerSdp);
825 var parsedOffer = new RTCSessionDescription({ type: 'offer',
826 sdp: offerSdp });
827 callee.setRemoteDescription(parsedOffer,
828 function() {
829 assertEquals('have-remote-offer',
830 callee.signalingState);
831 callee.createAnswer(
832 function (answer) {
833 onAnswerCreated(answer, caller, callee);
836 onRemoteDescriptionError);
839 function removeMsid(offerSdp) {
840 offerSdp = offerSdp.replace(/a=msid-semantic.*\r\n/g, '');
841 offerSdp = offerSdp.replace('a=mid:audio\r\n', '');
842 offerSdp = offerSdp.replace('a=mid:video\r\n', '');
843 offerSdp = offerSdp.replace(/a=ssrc.*\r\n/g, '');
844 return offerSdp;
847 function removeVideoCodec(offerSdp) {
848 offerSdp = offerSdp.replace('a=rtpmap:100 VP8/90000\r\n',
849 'a=rtpmap:100 XVP8/90000\r\n');
850 return offerSdp;
853 function removeCrypto(offerSdp) {
854 offerSdp = offerSdp.replace(/a=crypto.*\r\n/g, 'a=Xcrypto\r\n');
855 offerSdp = offerSdp.replace(/a=fingerprint.*\r\n/g, '');
856 return offerSdp;
859 function addBandwithControl(offerSdp) {
860 offerSdp = offerSdp.replace('a=mid:audio\r\n', 'a=mid:audio\r\n'+
861 'b=AS:16\r\n');
862 offerSdp = offerSdp.replace('a=mid:video\r\n', 'a=mid:video\r\n'+
863 'b=AS:512\r\n');
864 return offerSdp;
867 function removeBundle(sdp) {
868 return sdp.replace(/a=group:BUNDLE .*\r\n/g, '');
871 function useGice(sdp) {
872 sdp = sdp.replace(/t=.*\r\n/g, function(subString) {
873 return subString + 'a=ice-options:google-ice\r\n';
875 return sdp;
878 function useExternalSdes(sdp) {
879 // Remove current crypto specification.
880 sdp = sdp.replace(/a=crypto.*\r\n/g, '');
881 sdp = sdp.replace(/a=fingerprint.*\r\n/g, '');
882 // Add external crypto. This is not compatible with |removeMsid|.
883 sdp = sdp.replace(/a=mid:(\w+)\r\n/g, function(subString, group) {
884 return subString + EXTERNAL_SDES_LINES[group] + '\r\n';
886 return sdp;
889 function onAnswerCreated(answer, caller, callee) {
890 answer.sdp = maybeForceIsac16K(transformSdp(answer.sdp));
891 callee.setLocalDescription(answer,
892 function () {
893 assertEquals('stable', callee.signalingState);
895 onLocalDescriptionError);
896 receiveAnswer(answer.sdp, caller);
899 function receiveAnswer(answerSdp, caller) {
900 console.log("Receiving answer...");
901 answerSdp = transformRemoteSdp(answerSdp);
902 var parsedAnswer = new RTCSessionDescription({ type: 'answer',
903 sdp: answerSdp });
904 caller.setRemoteDescription(parsedAnswer,
905 function() {
906 assertEquals('stable', caller.signalingState);
908 onRemoteDescriptionError);
911 function connectOnIceCandidate(caller, callee) {
912 caller.onicecandidate = function(event) { onIceCandidate(event, callee); }
913 callee.onicecandidate = function(event) { onIceCandidate(event, caller); }
916 function onIceCandidate(event, target) {
917 if (event.candidate) {
918 var candidate = new RTCIceCandidate(event.candidate);
919 target.addIceCandidate(candidate);
923 function onRemoteStream(e, target) {
924 console.log("Receiving remote stream...");
925 if (gTestWithoutMsid && e.stream.id != "default") {
926 failTest('a default remote stream was expected but instead ' +
927 e.stream.id + ' was received.');
929 gRemoteStreams[target] = e.stream;
930 var remoteStreamUrl = URL.createObjectURL(e.stream);
931 var remoteVideo = $(target);
932 remoteVideo.src = remoteStreamUrl;
935 </script>
936 </head>
937 <body>
938 <table border="0">
939 <tr>
940 <td><video width="320" height="240" id="local-view" style="display:none"
941 autoplay muted></video></td>
942 <td><video width="320" height="240" id="remote-view-1"
943 style="display:none" autoplay></video></td>
944 <td><video width="320" height="240" id="remote-view-2"
945 style="display:none" autoplay></video></td>
946 <td><video width="320" height="240" id="remote-view-3"
947 style="display:none" autoplay></video></td>
948 <td><video width="320" height="240" id="remote-view-4"
949 style="display:none" autoplay></video></td>
950 <!-- Canvases are named after their corresponding video elements. -->
951 <td><canvas width="320" height="240" id="remote-view-1-canvas"
952 style="display:none"></canvas></td>
953 <td><canvas width="320" height="240" id="remote-view-2-canvas"
954 style="display:none"></canvas></td>
955 <td><canvas width="320" height="240" id="remote-view-3-canvas"
956 style="display:none"></canvas></td>
957 <td><canvas width="320" height="240" id="remote-view-4-canvas"
958 style="display:none"></canvas></td>
959 </tr>
960 </table>
961 </body>
962 </html>