Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / content / test / data / media / peerconnection-call.html
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   };
9
10   window.onerror = function(errorMsg, url, lineNumber, column, errorObj) {
11     failTest('Error: ' + errorMsg + '\nScript: ' + url +
12              '\nLine: ' + lineNumber + '\nColumn: ' + column +
13              '\nStackTrace: ' + errorObj);
14   }
15
16   var gFirstConnection = null;
17   var gSecondConnection = null;
18   var gTestWithoutMsid = false;
19   var gLocalStream = null;
20   var gSentTones = '';
21
22   var gRemoteStreams = {};
23
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); };
29
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;
38
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.');
44
45       return sdp;
46     };
47     sendValueToTest('isac-forced');
48   }
49
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'
58   };
59
60   setAllEventsOccuredHandler(reportTestSuccess);
61
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');
69   }
70
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',
78         function () {
79           assertEquals(gLocalStream.getVideoTracks().length, 1);
80           gLocalStream.getVideoTracks()[0].enabled = false;
81           waitForBlackVideo('remote-view-1');
82         });
83   }
84
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,
88                                    expected_width,
89                                    expected_height) {
90     createConnections(null);
91     navigator.webkitGetUserMedia(constraints,
92       addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
93     waitForVideoWithResolution('remote-view-1',
94                                expected_width,
95                                expected_height);
96     waitForVideoWithResolution('remote-view-2',
97                                expected_width,
98                                expected_height);
99   }
100
101
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);
108     negotiate();
109     waitForConnectionToStabilize(gFirstConnection, function() {
110       navigator.webkitGetUserMedia(constraints,
111         addStreamToTheFirstConnectionAndNegotiate, printGetUserMediaError);
112       // Only the first connection is sending here.
113       waitForVideo('remote-view-2');
114     });
115   }
116
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;
128
129     var onRemoteStream1 = function() {
130       gotRemoteStream1 = true;
131       maybeCallEstablished();
132     }
133
134     var onRemoteStream2 = function() {
135       gotRemoteStream2 = true;
136       maybeCallEstablished();
137     }
138
139     var maybeCallEstablished = function() {
140       if (gotRemoteStream1 && gotRemoteStream2) {
141         onCallEstablished();
142       }
143     }
144
145     var onCallEstablished = function() {
146       thirdConnection = createConnection(null, 'remote-view-3');
147       thirdConnection.addStream(gRemoteStreams['remote-view-1']);
148
149       fourthConnection = createConnection(null, 'remote-view-4');
150       fourthConnection.addStream(gRemoteStreams['remote-view-2'].clone());
151
152       negotiateBetween(thirdConnection, fourthConnection);
153
154       waitForVideo('remote-view-3');
155       waitForVideo('remote-view-4');
156     }
157
158     // Do the forwarding after we have received video.
159     detectVideoPlaying('remote-view-1', onRemoteStream1);
160     detectVideoPlaying('remote-view-2', onRemoteStream2);
161   }
162
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);
171
172     detectVideoPlaying('remote-view-2', function() {
173       // Construct a new media stream with remote tracks.
174       var newStream = new webkitMediaStream();
175       newStream.addTrack(
176           gSecondConnection.getRemoteStreams()[0].getAudioTracks()[0]);
177       newStream.addTrack(
178           gSecondConnection.getRemoteStreams()[0].getVideoTracks()[0]);
179       var videoElement = document.createElement('video');
180
181       // No crash for this operation.
182       videoElement.src = URL.createObjectURL(newStream);
183       waitForVideo('remote-view-2');
184     });
185   }
186
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');
198   }
199
200   // Test that we can't setup a call with an unsupported video codec
201   function negotiateUnsupportedVideoCodec() {
202     createConnections(null);
203     transformSdp = removeVideoCodec;
204
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);
210       reportTestSuccess();
211     };
212     navigator.webkitGetUserMedia({audio: true, video: true},
213         addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
214   }
215
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.';
223
224       assertEquals(expectedMsg, error);
225       reportTestSuccess();
226     };
227     navigator.webkitGetUserMedia({audio: true, video: true},
228         addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
229   }
230
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');
240   }
241
242   // Test that we can setup call with legacy settings.
243   function callWithLegacySdp() {
244     transformSdp = function(sdp) {
245       return removeBundle(useGice(useExternalSdes(sdp)));
246     };
247     createConnections({
248       'mandatory': {'RtpDataChannels': true, 'DtlsSrtpKeyAgreement': false}
249     });
250     setupDataChannel({reliable: false});
251     navigator.webkitGetUserMedia({audio: true, video: true},
252         addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
253     waitForVideo('remote-view-1');
254     waitForVideo('remote-view-2');
255   }
256
257   // Test only a data channel.
258   function callWithDataOnly() {
259     createConnections({optional:[{RtpDataChannels: true}]});
260     setupDataChannel({reliable: false});
261     negotiate();
262   }
263
264   function callWithSctpDataOnly() {
265     createConnections({optional: [{DtlsSrtpKeyAgreement: true}]});
266     setupSctpDataChannel({reliable: true});
267     negotiate();
268   }
269
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');
279   }
280
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');
289   }
290
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});
295     negotiate();
296
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');
305     });
306   }
307
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() {
314       // Send DTMF tones.
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.
320       addExpectedEvent();
321       var waitDtmf = setInterval(function() {
322         if (gSentTones == tones) {
323           clearInterval(waitDtmf);
324           eventOccured();
325         }
326       }, 100);
327     }
328
329     // Do the DTMF test after we have received video.
330     detectVideoPlaying('remote-view-2', onCallEstablished);
331   }
332
333   function testCreateOfferOptions() {
334     createConnections(null);
335     var offerOptions = {
336       'offerToReceiveAudio': false,
337       'offerToReceiveVideo': true
338     };
339
340     gFirstConnection.createOffer(
341         function(offer) {
342           assertEquals(-1, offer.sdp.search('m=audio'));
343           assertNotEquals(-1, offer.sdp.search('m=video'));
344
345           reportTestSuccess();
346         },
347         function(error) { failTest(error); },
348         offerOptions);
349   }
350
351   function callAndEnsureAudioIsPlaying(beLenient, constraints) {
352     createConnections(null);
353
354     // Add the local stream to gFirstConnection to play one-way audio.
355     navigator.webkitGetUserMedia(constraints,
356       addStreamToTheFirstConnectionAndNegotiate, printGetUserMediaError);
357
358     var onCallEstablished = function() {
359       ensureAudioPlaying(gSecondConnection, beLenient);
360     };
361
362     waitForConnectionToStabilize(gFirstConnection, onCallEstablished);
363   }
364
365   function enableRemoteVideo(peerConnection, enabled) {
366     remoteStream = peerConnection.getRemoteStreams()[0];
367     remoteStream.getVideoTracks()[0].enabled = enabled;
368   }
369
370   function enableRemoteAudio(peerConnection, enabled) {
371     remoteStream = peerConnection.getRemoteStreams()[0];
372     remoteStream.getAudioTracks()[0].enabled = enabled;
373   }
374
375   function enableLocalVideo(peerConnection, enabled) {
376     localStream = peerConnection.getLocalStreams()[0];
377     localStream.getVideoTracks()[0].enabled = enabled;
378   }
379
380   function enableLocalAudio(peerConnection, enabled) {
381     localStream = peerConnection.getLocalStreams()[0];
382     localStream.getAudioTracks()[0].enabled = enabled;
383   }
384
385   function callAndEnsureRemoteAudioTrackMutingWorks(beLenient) {
386     callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
387     setAllEventsOccuredHandler(function() {
388       setAllEventsOccuredHandler(reportTestSuccess);
389
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);
394     });
395   }
396
397   function callAndEnsureLocalAudioTrackMutingWorks(beLenient) {
398     callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
399     setAllEventsOccuredHandler(function() {
400       setAllEventsOccuredHandler(reportTestSuccess);
401
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);
406     });
407   }
408
409   function callAndEnsureAudioTrackUnmutingWorks(beLenient) {
410     callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
411     setAllEventsOccuredHandler(function() {
412       setAllEventsOccuredHandler(reportTestSuccess);
413
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);
418
419       setTimeout(function() {
420         enableRemoteAudio(gSecondConnection, true);
421       }, 500);
422
423       setTimeout(function() {
424         ensureAudioPlaying(gSecondConnection, beLenient);
425       }, 1500);
426     });
427   }
428
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);
435     });
436   }
437
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);
444     });
445   }
446
447   function callAndEnsureVideoTrackMutingWorks() {
448     createConnections(null);
449     navigator.webkitGetUserMedia({audio: true, video: true},
450       addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
451
452     addExpectedEvent();
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);
458
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);
463       })
464     });
465   }
466
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');
475   }
476
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);
486
487     waitForVideo('remote-view-1');
488     waitForVideo('remote-view-2');
489
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];
495
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
499       // received.
500       addExpectedEvent();
501       remote_stream_1.onaddtrack = function(){
502         assertEquals(remote_stream_1.getAudioTracks()[0].id,
503                      local_stream.getAudioTracks()[0].id);
504         eventOccured();
505       }
506
507       // Add an expectation that the received video track is removed from
508       // gFirstConnection.
509       addExpectedEvent();
510       remote_stream_1.onremovetrack = function() {
511         eventOccured();
512       }
513
514       // Add an expected event that onaddtrack will be called on the remote
515       // mediastream received on gSecondConnection when the audio track is
516       // received.
517       remote_stream_2 = gSecondConnection.getRemoteStreams()[0];
518       addExpectedEvent();
519       remote_stream_2.onaddtrack = function() {
520         assertEquals(remote_stream_2.getAudioTracks()[0].id,
521                      local_stream.getAudioTracks()[0].id);
522         eventOccured();
523       }
524
525       // Add an expectation that the received video track is removed from
526       // gSecondConnection.
527       addExpectedEvent();
528       remote_stream_2.onremovetrack = function() {
529         eventOccured();
530       }
531       // When all the above events have occurred- the test pass.
532       setAllEventsOccuredHandler(reportTestSuccess);
533
534       local_stream.addTrack(gLocalStream.getAudioTracks()[0]);
535       local_stream.removeTrack(local_stream.getVideoTracks()[0]);
536       negotiate();
537     });
538   }
539
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);
552
553     // When |firstDataChannel| transition to open state, send a text string.
554     firstDataChannel.onopen = function() {
555       assertEquals('open', firstDataChannel.readyState);
556       firstDataChannel.send(sendDataString);
557     }
558
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();
564       negotiate();
565     }
566
567     // When |firstDataChannel| transition to closed state, the test pass.
568     addExpectedEvent();
569     firstDataChannel.onclose = function() {
570       assertEquals('closed', firstDataChannel.readyState);
571       eventOccured();
572     }
573
574     // Event handler for when |gSecondConnection| receive a new dataChannel.
575     gSecondConnection.ondatachannel = function (event) {
576       var secondDataChannel = event.channel;
577
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);
584       }
585     }
586   }
587
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);
598
599     // When |firstDataChannel| transition to open state, send a text string.
600     firstDataChannel.onopen = function() {
601       assertEquals('open', firstDataChannel.readyState);
602     }
603
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);
610     }
611
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);
617       }
618
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();
625         negotiate();
626       }
627
628       // When |secondDataChannel| transition to closed state, the test pass.
629       addExpectedEvent();
630       secondDataChannel.onclose = function() {
631         assertEquals('closed', secondDataChannel.readyState);
632         eventOccured();
633       }
634     }
635   }
636
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);
643   }
644
645   function onToneChange(tone) {
646     gSentTones += tone.tone;
647   }
648
649   function createConnections(constraints) {
650     gFirstConnection = createConnection(constraints, 'remote-view-1');
651     assertEquals('stable', gFirstConnection.signalingState);
652
653     gSecondConnection = createConnection(constraints, 'remote-view-2');
654     assertEquals('stable', gSecondConnection.signalingState);
655   }
656
657   function createConnection(constraints, remoteView) {
658     var pc = new webkitRTCPeerConnection(null, constraints);
659     pc.onaddstream = function(event) {
660       onRemoteStream(event, remoteView);
661     }
662     return pc;
663   }
664
665   function displayAndRemember(localStream) {
666     var localStreamUrl = URL.createObjectURL(localStream);
667     $('local-view').src = localStreamUrl;
668
669     gLocalStream = localStream;
670   }
671
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;
677     else
678       message += ' devices not working/user denied access.';
679     failTest(message);
680   }
681
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);
687     negotiate();
688   }
689
690   // Called if getUserMedia succeeds when we want to send from one connection.
691   function addStreamToTheFirstConnectionAndNegotiate(localStream) {
692     displayAndRemember(localStream);
693     gFirstConnection.addStream(localStream);
694     negotiate();
695   }
696
697   function verifyHasOneAudioAndVideoTrack(stream) {
698     assertEquals(1, stream.getAudioTracks().length);
699     assertEquals(1, stream.getVideoTracks().length);
700   }
701
702   // Called if getUserMedia succeeds, then clone the stream, send two streams
703   // from one peer connection.
704   function CloneStreamAndAddTwoStreamstoOneConnection(localStream) {
705     displayAndRemember(localStream);
706
707     var clonedStream = null;
708     if (typeof localStream.clone === "function") {
709       clonedStream = localStream.clone();
710     } else {
711       clonedStream = new webkitMediaStream(localStream);
712     }
713
714     gFirstConnection.addStream(localStream);
715     gFirstConnection.addStream(clonedStream);
716
717     // Verify the local streams are correct.
718     assertEquals(2, gFirstConnection.getLocalStreams().length);
719     verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[0]);
720     verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[1]);
721
722     // The remote side should receive two streams. After that, verify the
723     // remote side has the correct number of streams and tracks.
724     addExpectedEvent();
725     addExpectedEvent();
726     gSecondConnection.onaddstream = function(event) {
727       eventOccured();
728     }
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]);
734
735       reportTestSuccess();
736     });
737
738     negotiate();
739   }
740
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);
750     negotiate();
751   }
752
753   function negotiate() {
754     negotiateBetween(gFirstConnection, gSecondConnection);
755   }
756
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!';
763
764     connectOnIceCandidate(caller, callee);
765
766     caller.createOffer(
767         function (offer) {
768           onOfferCreated(offer, caller, callee);
769         });
770   }
771
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);
778   }
779
780   function receiveOffer(offerSdp, caller, callee) {
781     console.log("Receiving offer...");
782     offerSdp = transformRemoteSdp(offerSdp);
783
784     var parsedOffer = new RTCSessionDescription({ type: 'offer',
785                                                   sdp: offerSdp });
786     callee.setRemoteDescription(parsedOffer,
787                                 function() {
788                                   assertEquals('have-remote-offer',
789                                                callee.signalingState);
790                                   callee.createAnswer(
791                                     function (answer) {
792                                       onAnswerCreated(answer, caller, callee);
793                                     });
794                                 },
795                                 onRemoteDescriptionError);
796   }
797
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, '');
803     return offerSdp;
804   }
805
806   function removeVideoCodec(offerSdp) {
807     offerSdp = offerSdp.replace('a=rtpmap:100 VP8/90000\r\n',
808                                 'a=rtpmap:100 XVP8/90000\r\n');
809     return offerSdp;
810   }
811
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, '');
815     return offerSdp;
816   }
817
818   function addBandwithControl(offerSdp) {
819     offerSdp = offerSdp.replace('a=mid:audio\r\n', 'a=mid:audio\r\n'+
820                                 'b=AS:16\r\n');
821     offerSdp = offerSdp.replace('a=mid:video\r\n', 'a=mid:video\r\n'+
822                                 'b=AS:512\r\n');
823     return offerSdp;
824   }
825
826   function removeBundle(sdp) {
827     return sdp.replace(/a=group:BUNDLE .*\r\n/g, '');
828   }
829
830   function useGice(sdp) {
831     sdp = sdp.replace(/t=.*\r\n/g, function(subString) {
832       return subString + 'a=ice-options:google-ice\r\n';
833     });
834     return sdp;
835   }
836
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';
844     });
845     return sdp;
846   }
847
848   function onAnswerCreated(answer, caller, callee) {
849     answer.sdp = maybeForceIsac16K(transformSdp(answer.sdp));
850     callee.setLocalDescription(answer,
851                                function () {
852                                  assertEquals('stable', callee.signalingState);
853                                },
854                                onLocalDescriptionError);
855     receiveAnswer(answer.sdp, caller);
856   }
857
858   function receiveAnswer(answerSdp, caller) {
859     console.log("Receiving answer...");
860     answerSdp = transformRemoteSdp(answerSdp);
861     var parsedAnswer = new RTCSessionDescription({ type: 'answer',
862                                                    sdp: answerSdp });
863     caller.setRemoteDescription(parsedAnswer,
864                                 function() {
865                                   assertEquals('stable', caller.signalingState);
866                                 },
867                                 onRemoteDescriptionError);
868   }
869
870   function connectOnIceCandidate(caller, callee) {
871     caller.onicecandidate = function(event) { onIceCandidate(event, callee); }
872     callee.onicecandidate = function(event) { onIceCandidate(event, caller); }
873   }
874
875   function onIceCandidate(event, target) {
876     if (event.candidate) {
877       var candidate = new RTCIceCandidate(event.candidate);
878       target.addIceCandidate(candidate);
879     }
880   }
881
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.');
887     }
888     gRemoteStreams[target] = e.stream;
889     var remoteStreamUrl = URL.createObjectURL(e.stream);
890     var remoteVideo = $(target);
891     remoteVideo.src = remoteStreamUrl;
892   }
893
894   </script>
895 </head>
896 <body>
897   <table border="0">
898     <tr>
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>
918     </tr>
919   </table>
920 </body>
921 </html>