Roll src/third_party/WebKit e0eac24:489c548 (svn 193311:193320)
[chromium-blink-merge.git] / content / test / data / media / peerconnection-call.html
blob68fd5c06df8e89362edef7f12ba119a80a95c0ff
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 where a stream is sent from pc1 to
160 // pc2. The stream sent from pc1 to pc2 is cloned from the stream received on
161 // pc2 to test that cloning of remote video tracks works as intended and is
162 // sent back to pc1.
163 function callAndForwardRemoteStream(constraints) {
164 createConnections(null);
165 navigator.webkitGetUserMedia(constraints,
166 addStreamToTheFirstConnectionAndNegotiate,
167 printGetUserMediaError);
168 var onRemoteStream2 = function() {
169 // Video has been detected to be playing in pc2. Clone the received
170 // stream and send it back to pc1.
171 gSecondConnection.addStream(gRemoteStreams['remote-view-2'].clone());
172 negotiate();
175 // Wait for remove video to be playing in pc2. Once video is playing,
176 // forward the remove stream from pc2 to pc1.
177 detectVideoPlaying('remote-view-2', onRemoteStream2);
179 // Wait for video to be forwarded back to connection 1.
180 waitForVideo('remote-view-1');
183 // First makes a call between pc1 and pc2, and then construct a new media
184 // stream using the remote audio and video tracks, connect the new media
185 // stream to a video element. These operations should not crash Chrome.
186 function ConnectChromiumSinkToRemoteAudioTrack() {
187 createConnections(null);
188 navigator.webkitGetUserMedia({audio: true, video: true},
189 addStreamToBothConnectionsAndNegotiate,
190 printGetUserMediaError);
192 detectVideoPlaying('remote-view-2', function() {
193 // Construct a new media stream with remote tracks.
194 var newStream = new webkitMediaStream();
195 newStream.addTrack(
196 gSecondConnection.getRemoteStreams()[0].getAudioTracks()[0]);
197 newStream.addTrack(
198 gSecondConnection.getRemoteStreams()[0].getVideoTracks()[0]);
199 var videoElement = document.createElement('video');
201 // No crash for this operation.
202 videoElement.src = URL.createObjectURL(newStream);
203 waitForVideo('remote-view-2');
207 // Test that we can setup call with an audio and video track and
208 // simulate that the remote peer don't support MSID.
209 function callWithoutMsidAndBundle() {
210 createConnections(null);
211 transformSdp = removeBundle;
212 transformRemoteSdp = removeMsid;
213 gTestWithoutMsid = true;
214 navigator.webkitGetUserMedia({audio: true, video: true},
215 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
216 waitForVideo('remote-view-1');
217 waitForVideo('remote-view-2');
220 // Test that we can't setup a call with an unsupported video codec
221 function negotiateUnsupportedVideoCodec() {
222 createConnections(null);
223 transformSdp = removeVideoCodec;
225 onLocalDescriptionError = function(error) {
226 var expectedMsg = 'Failed to set local offer sdp:' +
227 ' Session error code: ERROR_CONTENT. Session error description:' +
228 ' Failed to set video receive codecs..';
229 assertEquals(expectedMsg, error);
230 reportTestSuccess();
232 navigator.webkitGetUserMedia({audio: true, video: true},
233 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
236 // Test that we can't setup a call if one peer does not support encryption
237 function negotiateNonCryptoCall() {
238 createConnections(null);
239 transformSdp = removeCrypto;
240 onLocalDescriptionError = function(error) {
241 var expectedMsg = 'Failed to set local offer sdp:' +
242 ' Called with SDP without DTLS fingerprint.';
244 assertEquals(expectedMsg, error);
245 reportTestSuccess();
247 navigator.webkitGetUserMedia({audio: true, video: true},
248 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
251 // Test that we can negotiate a call with an SDP offer that includes a
252 // b=AS:XX line to control audio and video bandwidth
253 function negotiateOfferWithBLine() {
254 createConnections(null);
255 transformSdp = addBandwithControl;
256 navigator.webkitGetUserMedia({audio: true, video: true},
257 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
258 waitForVideo('remote-view-1');
259 waitForVideo('remote-view-2');
262 // Test that we can setup call with legacy settings.
263 function callWithLegacySdp() {
264 transformSdp = function(sdp) {
265 return removeBundle(useGice(useExternalSdes(sdp)));
267 createConnections({
268 'mandatory': {'RtpDataChannels': true, 'DtlsSrtpKeyAgreement': false}
270 setupDataChannel({reliable: false});
271 navigator.webkitGetUserMedia({audio: true, video: true},
272 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
273 waitForVideo('remote-view-1');
274 waitForVideo('remote-view-2');
277 // Test only a data channel.
278 function callWithDataOnly() {
279 createConnections({optional:[{RtpDataChannels: true}]});
280 setupDataChannel({reliable: false});
281 negotiate();
284 function callWithSctpDataOnly() {
285 createConnections({optional: [{DtlsSrtpKeyAgreement: true}]});
286 setupSctpDataChannel({reliable: true});
287 negotiate();
290 // Test call with audio, video and a data channel.
291 function callWithDataAndMedia() {
292 createConnections({optional:[{RtpDataChannels: true}]});
293 setupDataChannel({reliable: false});
294 navigator.webkitGetUserMedia({audio: true, video: true},
295 addStreamToBothConnectionsAndNegotiate,
296 printGetUserMediaError);
297 waitForVideo('remote-view-1');
298 waitForVideo('remote-view-2');
301 function callWithSctpDataAndMedia() {
302 createConnections({optional: [{DtlsSrtpKeyAgreement: true}]});
303 setupSctpDataChannel({reliable: true});
304 navigator.webkitGetUserMedia({audio: true, video: true},
305 addStreamToBothConnectionsAndNegotiate,
306 printGetUserMediaError);
307 waitForVideo('remote-view-1');
308 waitForVideo('remote-view-2');
311 // Test call with a data channel and later add audio and video.
312 function callWithDataAndLaterAddMedia() {
313 createConnections({optional:[{RtpDataChannels: true}]});
314 setupDataChannel({reliable: false});
315 negotiate();
317 // Set an event handler for when the data channel has been closed.
318 setAllEventsOccuredHandler(function() {
319 // When the video is flowing the test is done.
320 setAllEventsOccuredHandler(reportTestSuccess);
321 navigator.webkitGetUserMedia({audio: true, video: true},
322 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
323 waitForVideo('remote-view-1');
324 waitForVideo('remote-view-2');
328 // Test that we can setup call and send DTMF.
329 function callAndSendDtmf(tones) {
330 createConnections(null);
331 navigator.webkitGetUserMedia({audio: true, video: true},
332 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
333 var onCallEstablished = function() {
334 // Send DTMF tones.
335 var localAudioTrack = gLocalStream.getAudioTracks()[0];
336 var dtmfSender = gFirstConnection.createDTMFSender(localAudioTrack);
337 dtmfSender.ontonechange = onToneChange;
338 dtmfSender.insertDTMF(tones);
339 // Wait for the DTMF tones callback.
340 addExpectedEvent();
341 var waitDtmf = setInterval(function() {
342 if (gSentTones == tones) {
343 clearInterval(waitDtmf);
344 eventOccured();
346 }, 100);
349 // Do the DTMF test after we have received video.
350 detectVideoPlaying('remote-view-2', onCallEstablished);
353 function testCreateOfferOptions() {
354 createConnections(null);
355 var offerOptions = {
356 'offerToReceiveAudio': false,
357 'offerToReceiveVideo': true
360 gFirstConnection.createOffer(
361 function(offer) {
362 assertEquals(-1, offer.sdp.search('m=audio'));
363 assertNotEquals(-1, offer.sdp.search('m=video'));
365 reportTestSuccess();
367 function(error) { failTest(error); },
368 offerOptions);
371 function callAndEnsureAudioIsPlaying(beLenient, constraints) {
372 createConnections(null);
374 // Add the local stream to gFirstConnection to play one-way audio.
375 navigator.webkitGetUserMedia(constraints,
376 addStreamToTheFirstConnectionAndNegotiate, printGetUserMediaError);
378 var onCallEstablished = function() {
379 ensureAudioPlaying(gSecondConnection, beLenient);
382 waitForConnectionToStabilize(gFirstConnection, onCallEstablished);
385 function enableRemoteVideo(peerConnection, enabled) {
386 remoteStream = peerConnection.getRemoteStreams()[0];
387 remoteStream.getVideoTracks()[0].enabled = enabled;
390 function enableRemoteAudio(peerConnection, enabled) {
391 remoteStream = peerConnection.getRemoteStreams()[0];
392 remoteStream.getAudioTracks()[0].enabled = enabled;
395 function enableLocalVideo(peerConnection, enabled) {
396 localStream = peerConnection.getLocalStreams()[0];
397 localStream.getVideoTracks()[0].enabled = enabled;
400 function enableLocalAudio(peerConnection, enabled) {
401 localStream = peerConnection.getLocalStreams()[0];
402 localStream.getAudioTracks()[0].enabled = enabled;
405 function callAndEnsureRemoteAudioTrackMutingWorks(beLenient) {
406 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
407 setAllEventsOccuredHandler(function() {
408 setAllEventsOccuredHandler(reportTestSuccess);
410 // Call is up, now mute the remote track and check we stop playing out
411 // audio (after a small delay, we don't expect it to happen instantly).
412 enableRemoteAudio(gSecondConnection, false);
413 ensureSilence(gSecondConnection);
417 function callAndEnsureLocalAudioTrackMutingWorks(beLenient) {
418 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
419 setAllEventsOccuredHandler(function() {
420 setAllEventsOccuredHandler(reportTestSuccess);
422 // Call is up, now mute the local track of the sending side and ensure
423 // the receiving side stops receiving audio.
424 enableLocalAudio(gFirstConnection, false);
425 ensureSilence(gSecondConnection);
429 function callAndEnsureAudioTrackUnmutingWorks(beLenient) {
430 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
431 setAllEventsOccuredHandler(function() {
432 setAllEventsOccuredHandler(reportTestSuccess);
434 // Mute, wait a while, unmute, verify audio gets back up.
435 // (Also, ensure video muting doesn't affect audio).
436 enableRemoteAudio(gSecondConnection, false);
437 enableRemoteVideo(gSecondConnection, false);
439 setTimeout(function() {
440 enableRemoteAudio(gSecondConnection, true);
441 }, 500);
443 setTimeout(function() {
444 ensureAudioPlaying(gSecondConnection, beLenient);
445 }, 1500);
449 function callAndEnsureLocalVideoMutingDoesntMuteAudio(beLenient) {
450 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
451 setAllEventsOccuredHandler(function() {
452 setAllEventsOccuredHandler(reportTestSuccess);
453 enableLocalVideo(gFirstConnection, false);
454 ensureAudioPlaying(gSecondConnection, beLenient);
458 function callAndEnsureRemoteVideoMutingDoesntMuteAudio(beLenient) {
459 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
460 setAllEventsOccuredHandler(function() {
461 setAllEventsOccuredHandler(reportTestSuccess);
462 enableRemoteVideo(gSecondConnection, false);
463 ensureAudioPlaying(gSecondConnection, beLenient);
467 function callAndEnsureVideoTrackMutingWorks() {
468 createConnections(null);
469 navigator.webkitGetUserMedia({audio: true, video: true},
470 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
472 addExpectedEvent();
473 detectVideoPlaying('remote-view-2', function() {
474 // Disable the receiver's remote media stream. Video should stop.
475 // (Also, ensure muting audio doesn't affect video).
476 enableRemoteVideo(gSecondConnection, false);
477 enableRemoteAudio(gSecondConnection, false);
479 detectVideoStopped('remote-view-2', function() {
480 // Video has stopped: unmute and succeed if it starts playing again.
481 enableRemoteVideo(gSecondConnection, true);
482 detectVideoPlaying('remote-view-2', eventOccured);
487 // Test call with a new Video MediaStream that has been created based on a
488 // stream generated by getUserMedia.
489 function callWithNewVideoMediaStream() {
490 createConnections(null);
491 navigator.webkitGetUserMedia({audio: true, video: true},
492 createNewVideoStreamAndAddToBothConnections, printGetUserMediaError);
493 waitForVideo('remote-view-1');
494 waitForVideo('remote-view-2');
497 // Test call with a new Video MediaStream that has been created based on a
498 // stream generated by getUserMedia. When Video is flowing, an audio track
499 // is added to the sent stream and the video track is removed. This
500 // is to test that adding and removing of remote tracks on an existing
501 // mediastream works.
502 function callWithNewVideoMediaStreamLaterSwitchToAudio() {
503 createConnections(null);
504 navigator.webkitGetUserMedia({audio: true, video: true},
505 createNewVideoStreamAndAddToBothConnections, printGetUserMediaError);
507 waitForVideo('remote-view-1');
508 waitForVideo('remote-view-2');
510 // Set an event handler for when video is playing.
511 setAllEventsOccuredHandler(function() {
512 // Add an audio track to the local stream and remove the video track and
513 // then renegotiate. But first - setup the expectations.
514 var localStream = gFirstConnection.getLocalStreams()[0];
515 var remoteStream1 = gFirstConnection.getRemoteStreams()[0];
517 // Add an expected event that onaddtrack will be called on the remote
518 // mediastream received on gFirstConnection when the audio track is
519 // received.
520 addExpectedEvent();
521 remoteStream1.onaddtrack = function(){
522 assertEquals(remoteStream1.getAudioTracks()[0].id,
523 localStream.getAudioTracks()[0].id);
524 eventOccured();
527 // Add an expectation that the received video track is removed from
528 // gFirstConnection.
529 addExpectedEvent();
530 remoteStream1.onremovetrack = function() {
531 eventOccured();
534 // Add an expected event that onaddtrack will be called on the remote
535 // mediastream received on gSecondConnection when the audio track is
536 // received.
537 remoteStream2 = gSecondConnection.getRemoteStreams()[0];
538 addExpectedEvent();
539 remoteStream2.onaddtrack = function() {
540 assertEquals(remoteStream2.getAudioTracks()[0].id,
541 localStream.getAudioTracks()[0].id);
542 eventOccured();
545 // Add an expectation that the received video track is removed from
546 // gSecondConnection.
547 addExpectedEvent();
548 remoteStream2.onremovetrack = function() {
549 eventOccured();
551 // When all the above events have occurred- the test pass.
552 setAllEventsOccuredHandler(reportTestSuccess);
554 localStream.addTrack(gLocalStream.getAudioTracks()[0]);
555 localStream.removeTrack(localStream.getVideoTracks()[0]);
556 negotiate();
560 // This function is used for setting up a test that:
561 // 1. Creates a data channel on |gFirstConnection| and sends data to
562 // |gSecondConnection|.
563 // 2. When data is received on |gSecondConnection| a message
564 // is sent to |gFirstConnection|.
565 // 3. When data is received on |gFirstConnection|, the data
566 // channel is closed. The test passes when the state transition completes.
567 function setupDataChannel(params) {
568 var sendDataString = "send some text on a data channel."
569 firstDataChannel = gFirstConnection.createDataChannel(
570 "sendDataChannel", params);
571 assertEquals('connecting', firstDataChannel.readyState);
573 // When |firstDataChannel| transition to open state, send a text string.
574 firstDataChannel.onopen = function() {
575 assertEquals('open', firstDataChannel.readyState);
576 firstDataChannel.send(sendDataString);
579 // When |firstDataChannel| receive a message, close the channel and
580 // initiate a new offer/answer exchange to complete the closure.
581 firstDataChannel.onmessage = function(event) {
582 assertEquals(event.data, sendDataString);
583 firstDataChannel.close();
584 negotiate();
587 // When |firstDataChannel| transition to closed state, the test pass.
588 addExpectedEvent();
589 firstDataChannel.onclose = function() {
590 assertEquals('closed', firstDataChannel.readyState);
591 eventOccured();
594 // Event handler for when |gSecondConnection| receive a new dataChannel.
595 gSecondConnection.ondatachannel = function (event) {
596 // Make secondDataChannel global to make sure it's not gc'd.
597 secondDataChannel = event.channel;
599 // When |secondDataChannel| receive a message, send a message back.
600 secondDataChannel.onmessage = function(event) {
601 assertEquals(event.data, sendDataString);
602 console.log("gSecondConnection received data");
603 assertEquals('open', secondDataChannel.readyState);
604 secondDataChannel.send(sendDataString);
609 // SCTP data channel setup is slightly different then RTP based
610 // channels. Due to a bug in libjingle, we can't send data immediately
611 // after channel becomes open. So for that reason in SCTP,
612 // we are sending data from second channel, when ondatachannel event is
613 // received. So data flow happens 2 -> 1 -> 2.
614 function setupSctpDataChannel(params) {
615 var sendDataString = "send some text on a data channel."
616 firstDataChannel = gFirstConnection.createDataChannel(
617 "sendDataChannel", params);
618 assertEquals('connecting', firstDataChannel.readyState);
620 // When |firstDataChannel| transition to open state, send a text string.
621 firstDataChannel.onopen = function() {
622 assertEquals('open', firstDataChannel.readyState);
625 // When |firstDataChannel| receive a message, send message back.
626 // initiate a new offer/answer exchange to complete the closure.
627 firstDataChannel.onmessage = function(event) {
628 assertEquals('open', firstDataChannel.readyState);
629 assertEquals(event.data, sendDataString);
630 firstDataChannel.send(sendDataString);
633 // Event handler for when |gSecondConnection| receive a new dataChannel.
634 gSecondConnection.ondatachannel = function (event) {
635 // Make secondDataChannel global to make sure it's not gc'd.
636 secondDataChannel = event.channel;
637 secondDataChannel.onopen = function() {
638 secondDataChannel.send(sendDataString);
641 // When |secondDataChannel| receive a message, close the channel and
642 // initiate a new offer/answer exchange to complete the closure.
643 secondDataChannel.onmessage = function(event) {
644 assertEquals(event.data, sendDataString);
645 assertEquals('open', secondDataChannel.readyState);
646 secondDataChannel.close();
647 negotiate();
650 // When |secondDataChannel| transition to closed state, the test pass.
651 addExpectedEvent();
652 secondDataChannel.onclose = function() {
653 assertEquals('closed', secondDataChannel.readyState);
654 eventOccured();
659 // Test call with a stream that has been created by getUserMedia, clone
660 // the stream to a cloned stream, send them via the same peer connection.
661 function addTwoMediaStreamsToOneConnection() {
662 createConnections(null);
663 navigator.webkitGetUserMedia({audio: true, video: true},
664 cloneStreamAndAddTwoStreamsToOneConnection, printGetUserMediaError);
667 function onToneChange(tone) {
668 gSentTones += tone.tone;
671 function createConnections(constraints) {
672 gFirstConnection = createConnection(constraints, 'remote-view-1');
673 assertEquals('stable', gFirstConnection.signalingState);
675 gSecondConnection = createConnection(constraints, 'remote-view-2');
676 assertEquals('stable', gSecondConnection.signalingState);
679 function createConnection(constraints, remoteView) {
680 var pc = new webkitRTCPeerConnection(null, constraints);
681 pc.onaddstream = function(event) {
682 onRemoteStream(event, remoteView);
684 return pc;
687 function displayAndRemember(localStream) {
688 var localStreamUrl = URL.createObjectURL(localStream);
689 $('local-view').src = localStreamUrl;
691 gLocalStream = localStream;
694 // Called if getUserMedia fails.
695 function printGetUserMediaError(error) {
696 var message = 'getUserMedia request unexpectedly failed:';
697 if (error.constraintName)
698 message += ' could not satisfy constraint ' + error.constraintName;
699 else
700 message += ' devices not working/user denied access.';
701 failTest(message);
704 // Called if getUserMedia succeeds and we want to send from both connections.
705 function addStreamToBothConnectionsAndNegotiate(localStream) {
706 displayAndRemember(localStream);
707 gFirstConnection.addStream(localStream);
708 gSecondConnection.addStream(localStream);
709 negotiate();
712 // Called if getUserMedia succeeds when we want to send from one connection.
713 function addStreamToTheFirstConnectionAndNegotiate(localStream) {
714 displayAndRemember(localStream);
715 gFirstConnection.addStream(localStream);
716 negotiate();
719 function verifyHasOneAudioAndVideoTrack(stream) {
720 assertEquals(1, stream.getAudioTracks().length);
721 assertEquals(1, stream.getVideoTracks().length);
724 // Called if getUserMedia succeeds, then clone the stream, send two streams
725 // from one peer connection.
726 function cloneStreamAndAddTwoStreamsToOneConnection(localStream) {
727 displayAndRemember(localStream);
729 var clonedStream = null;
730 if (typeof localStream.clone === "function") {
731 clonedStream = localStream.clone();
732 } else {
733 clonedStream = new webkitMediaStream(localStream);
736 gFirstConnection.addStream(localStream);
737 gFirstConnection.addStream(clonedStream);
739 // Verify the local streams are correct.
740 assertEquals(2, gFirstConnection.getLocalStreams().length);
741 verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[0]);
742 verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[1]);
744 // The remote side should receive two streams. After that, verify the
745 // remote side has the correct number of streams and tracks.
746 addExpectedEvent();
747 addExpectedEvent();
748 gSecondConnection.onaddstream = function(event) {
749 eventOccured();
751 setAllEventsOccuredHandler(function() {
752 // Negotiation complete, verify remote streams on the receiving side.
753 assertEquals(2, gSecondConnection.getRemoteStreams().length);
754 verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[0]);
755 verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[1]);
757 reportTestSuccess();
760 negotiate();
763 // A new MediaStream is created with video track from |localStream| and is
764 // added to both peer connections.
765 function createNewVideoStreamAndAddToBothConnections(localStream) {
766 displayAndRemember(localStream);
767 var newStream = new webkitMediaStream();
768 newStream.addTrack(localStream.getVideoTracks()[0]);
769 gFirstConnection.addStream(newStream);
770 gSecondConnection.addStream(newStream);
771 negotiate();
774 function negotiate() {
775 negotiateBetween(gFirstConnection, gSecondConnection);
778 function negotiateBetween(caller, callee) {
779 console.log("Negotiating call...");
780 // Not stable = negotiation is ongoing. The behavior of re-negotiating while
781 // a negotiation is ongoing is more or less undefined, so avoid this.
782 if (caller.signalingState != 'stable' || callee.signalingState != 'stable')
783 throw 'You can only negotiate when the connection is stable!';
785 connectOnIceCandidate(caller, callee);
787 caller.createOffer(
788 function (offer) {
789 onOfferCreated(offer, caller, callee);
793 function onOfferCreated(offer, caller, callee) {
794 offer.sdp = maybeForceIsac16K(transformSdp(offer.sdp));
795 caller.setLocalDescription(offer, function() {
796 assertEquals('have-local-offer', caller.signalingState);
797 receiveOffer(offer.sdp, caller, callee);
798 }, onLocalDescriptionError);
801 function receiveOffer(offerSdp, caller, callee) {
802 console.log("Receiving offer...");
803 offerSdp = transformRemoteSdp(offerSdp);
805 var parsedOffer = new RTCSessionDescription({ type: 'offer',
806 sdp: offerSdp });
807 callee.setRemoteDescription(parsedOffer,
808 function() {
809 assertEquals('have-remote-offer',
810 callee.signalingState);
811 callee.createAnswer(
812 function (answer) {
813 onAnswerCreated(answer, caller, callee);
816 onRemoteDescriptionError);
819 function removeMsid(offerSdp) {
820 offerSdp = offerSdp.replace(/a=msid-semantic.*\r\n/g, '');
821 offerSdp = offerSdp.replace('a=mid:audio\r\n', '');
822 offerSdp = offerSdp.replace('a=mid:video\r\n', '');
823 offerSdp = offerSdp.replace(/a=ssrc.*\r\n/g, '');
824 return offerSdp;
827 function removeVideoCodec(offerSdp) {
828 offerSdp = offerSdp.replace('a=rtpmap:100 VP8/90000\r\n',
829 'a=rtpmap:100 XVP8/90000\r\n');
830 return offerSdp;
833 function removeCrypto(offerSdp) {
834 offerSdp = offerSdp.replace(/a=crypto.*\r\n/g, 'a=Xcrypto\r\n');
835 offerSdp = offerSdp.replace(/a=fingerprint.*\r\n/g, '');
836 return offerSdp;
839 function addBandwithControl(offerSdp) {
840 offerSdp = offerSdp.replace('a=mid:audio\r\n', 'a=mid:audio\r\n'+
841 'b=AS:16\r\n');
842 offerSdp = offerSdp.replace('a=mid:video\r\n', 'a=mid:video\r\n'+
843 'b=AS:512\r\n');
844 return offerSdp;
847 function removeBundle(sdp) {
848 return sdp.replace(/a=group:BUNDLE .*\r\n/g, '');
851 function useGice(sdp) {
852 sdp = sdp.replace(/t=.*\r\n/g, function(subString) {
853 return subString + 'a=ice-options:google-ice\r\n';
855 return sdp;
858 function useExternalSdes(sdp) {
859 // Remove current crypto specification.
860 sdp = sdp.replace(/a=crypto.*\r\n/g, '');
861 sdp = sdp.replace(/a=fingerprint.*\r\n/g, '');
862 // Add external crypto. This is not compatible with |removeMsid|.
863 sdp = sdp.replace(/a=mid:(\w+)\r\n/g, function(subString, group) {
864 return subString + EXTERNAL_SDES_LINES[group] + '\r\n';
866 return sdp;
869 function onAnswerCreated(answer, caller, callee) {
870 answer.sdp = maybeForceIsac16K(transformSdp(answer.sdp));
871 callee.setLocalDescription(answer,
872 function () {
873 assertEquals('stable', callee.signalingState);
875 onLocalDescriptionError);
876 receiveAnswer(answer.sdp, caller);
879 function receiveAnswer(answerSdp, caller) {
880 console.log("Receiving answer...");
881 answerSdp = transformRemoteSdp(answerSdp);
882 var parsedAnswer = new RTCSessionDescription({ type: 'answer',
883 sdp: answerSdp });
884 caller.setRemoteDescription(parsedAnswer,
885 function() {
886 assertEquals('stable', caller.signalingState);
888 onRemoteDescriptionError);
891 function connectOnIceCandidate(caller, callee) {
892 caller.onicecandidate = function(event) { onIceCandidate(event, callee); }
893 callee.onicecandidate = function(event) { onIceCandidate(event, caller); }
896 function onIceCandidate(event, target) {
897 if (event.candidate) {
898 var candidate = new RTCIceCandidate(event.candidate);
899 target.addIceCandidate(candidate);
903 function onRemoteStream(e, target) {
904 console.log("Receiving remote stream...");
905 if (gTestWithoutMsid && e.stream.id != "default") {
906 failTest('a default remote stream was expected but instead ' +
907 e.stream.id + ' was received.');
909 gRemoteStreams[target] = e.stream;
910 var remoteStreamUrl = URL.createObjectURL(e.stream);
911 var remoteVideo = $(target);
912 remoteVideo.src = remoteStreamUrl;
915 </script>
916 </head>
917 <body>
918 <table border="0">
919 <tr>
920 <td><video width="320" height="240" id="local-view" style="display:none"
921 autoplay muted></video></td>
922 <td><video width="320" height="240" id="remote-view-1"
923 style="display:none" autoplay></video></td>
924 <td><video width="320" height="240" id="remote-view-2"
925 style="display:none" autoplay></video></td>
926 <td><video width="320" height="240" id="remote-view-3"
927 style="display:none" autoplay></video></td>
928 <td><video width="320" height="240" id="remote-view-4"
929 style="display:none" autoplay></video></td>
930 <!-- Canvases are named after their corresponding video elements. -->
931 <td><canvas width="320" height="240" id="remote-view-1-canvas"
932 style="display:none"></canvas></td>
933 <td><canvas width="320" height="240" id="remote-view-2-canvas"
934 style="display:none"></canvas></td>
935 <td><canvas width="320" height="240" id="remote-view-3-canvas"
936 style="display:none"></canvas></td>
937 <td><canvas width="320" height="240" id="remote-view-4-canvas"
938 style="display:none"></canvas></td>
939 </tr>
940 </table>
941 </body>
942 </html>