Upstream version 8.37.180.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   var gFirstConnection = null;
11   var gSecondConnection = null;
12   var gTestWithoutMsid = false;
13   var gLocalStream = null;
14   var gSentTones = '';
15
16   var gRemoteStreams = {};
17
18   // Default transform functions, overridden by some test cases.
19   var transformSdp = function(sdp) { return sdp; };
20   var transformRemoteSdp = function(sdp) { return sdp; };
21   var transformCandidate = function(candidate) { return candidate; };
22   var onLocalDescriptionError = function(error) { failTest(error); };
23   var onRemoteDescriptionError = function(error) { failTest(error); };
24
25   // Temporary measure to be able to force iSAC 16K where needed, particularly
26   // on Android. This applies to every test which is why it's implemented like
27   // this.
28   var maybeForceIsac16K = function(sdp) { return sdp; };
29   function forceIsac16KInSdp() {
30     maybeForceIsac16K = function(sdp) {
31       sdp = sdp.replace(/m=audio (\d+) RTP\/SAVPF.*\r\n/g,
32                         'm=audio $1 RTP/SAVPF 103 126\r\n');
33       sdp = sdp.replace('a=fmtp:111 minptime=10', 'a=fmtp:103 minptime=10');
34       if (sdp.search('a=rtpmap:103 ISAC/16000') == -1)
35         failTest('Missing iSAC 16K codec on Android; cannot force codec.');
36       return sdp;
37     };
38     sendValueToTest('isac-forced');
39   }
40
41   // When using external SDES, the crypto key is chosen by javascript.
42   var EXTERNAL_SDES_LINES = {
43     'audio': 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' +
44         'inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR',
45     'video': 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' +
46         'inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj',
47     'data': 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' +
48         'inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj'
49   };
50
51   // When using GICE, the ICE credentials can be chosen by javascript.
52   var EXTERNAL_GICE_UFRAG = '1234567890123456';
53   var EXTERNAL_GICE_PWD = '123456789012345678901234';
54
55   setAllEventsOccuredHandler(reportTestSuccess);
56
57   // Test that we can setup call with an audio and video track.
58   function call(constraints) {
59     createConnections(null);
60     navigator.webkitGetUserMedia(constraints,
61       addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
62     waitForVideo('remote-view-1');
63     waitForVideo('remote-view-2');
64   }
65
66   // Test that we can setup call with an audio and video track and check that
67   // the video resolution is as expected.
68   function callAndExpectResolution(constraints,
69                                    expected_width,
70                                    expected_height) {
71     createConnections(null);
72     navigator.webkitGetUserMedia(constraints,
73       addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
74     waitForVideoWithResolution('remote-view-1',
75                                expected_width,
76                                expected_height);
77     waitForVideoWithResolution('remote-view-2',
78                                expected_width,
79                                expected_height);
80   }
81
82
83   // First calls without streams on any connections, and then adds a stream
84   // to peer connection 1 which gets sent to peer connection 2. We must wait
85   // for the first negotiation to complete before starting the second one, which
86   // is why we wait until the connection is stable before re-negotiating.
87   function callEmptyThenAddOneStreamAndRenegotiate(constraints) {
88     createConnections(null);
89     negotiate();
90     waitForConnectionToStabilize(gFirstConnection, function() {
91       navigator.webkitGetUserMedia(constraints,
92         addStreamToTheFirstConnectionAndNegotiate, printGetUserMediaError);
93       // Only the first connection is sending here.
94       waitForVideo('remote-view-2');
95     });
96   }
97
98   // First makes a call between pc1 and pc2, and then makes a call between pc3
99   // and pc4. The stream sent from pc3 to pc4 is the stream received on pc1.
100   // The stream sent from pc4 to pc3 is cloned from the stream received on pc2
101   // to test that cloning of remote video tracks works as intended.
102   function callAndForwardRemoteStream(constraints) {
103     createConnections(null);
104     navigator.webkitGetUserMedia(constraints,
105                                  addStreamToBothConnectionsAndNegotiate,
106                                  printGetUserMediaError);
107     var gotRemoteStream1 = false;
108     var gotRemoteStream2 = false;
109
110     var onRemoteStream1 = function() {
111       gotRemoteStream1 = true;
112       maybeCallEstablished();
113     }
114
115     var onRemoteStream2 = function() {
116       gotRemoteStream2 = true;
117       maybeCallEstablished();
118     }
119
120     var maybeCallEstablished = function() {
121       if (gotRemoteStream1 && gotRemoteStream2) {
122         onCallEstablished();
123       }
124     }
125
126     var onCallEstablished = function() {
127       thirdConnection = createConnection(null, 'remote-view-3');
128       thirdConnection.addStream(gRemoteStreams['remote-view-1']);
129
130       fourthConnection = createConnection(null, 'remote-view-4');
131       fourthConnection.addStream(gRemoteStreams['remote-view-2'].clone());
132
133       negotiateBetween(thirdConnection, fourthConnection);
134
135       waitForVideo('remote-view-3');
136       waitForVideo('remote-view-4');
137     }
138
139     // Do the forwarding after we have received video.
140     detectVideoPlaying('remote-view-1', onRemoteStream1);
141     detectVideoPlaying('remote-view-2', onRemoteStream2);
142   }
143
144   // Test that we can setup call with an audio and video track and
145   // simulate that the remote peer don't support MSID.
146   function callWithoutMsidAndBundle() {
147     createConnections(null);
148     transformSdp = removeBundle;
149     transformRemoteSdp = removeMsid;
150     gTestWithoutMsid = true;
151     navigator.webkitGetUserMedia({audio: true, video: true},
152         addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
153     waitForVideo('remote-view-1');
154     waitForVideo('remote-view-2');
155   }
156
157   // Test that we can't setup a call with an unsupported video codec
158   function negotiateUnsupportedVideoCodec() {
159     createConnections(null);
160     transformSdp = removeVideoCodec;
161
162     onLocalDescriptionError = function(error) {
163       var expectedMsg = 'Failed to set local offer sdp:' +
164           ' Session error code: ERROR_CONTENT. Session error description:' +
165               ' Failed to set video receive codecs..';
166       assertEquals(expectedMsg, error);
167       reportTestSuccess();
168     };
169     navigator.webkitGetUserMedia({audio: true, video: true},
170         addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
171   }
172
173   // Test that we can't setup a call if one peer does not support encryption
174   function negotiateNonCryptoCall() {
175     createConnections(null);
176     transformSdp = removeCrypto;
177     onLocalDescriptionError = function(error) {
178       var expectedMsg = 'Failed to set local offer sdp:' +
179           ' Called with SDP without DTLS fingerprint.';
180
181       assertEquals(expectedMsg, error);
182       reportTestSuccess();
183     };
184     navigator.webkitGetUserMedia({audio: true, video: true},
185         addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
186   }
187
188   // Test that we can negotiate a call with an SDP offer that includes a
189   // b=AS:XX line to control audio and video bandwidth
190   function negotiateOfferWithBLine() {
191     createConnections(null);
192     transformSdp = addBandwithControl;
193     navigator.webkitGetUserMedia({audio: true, video: true},
194         addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
195     waitForVideo('remote-view-1');
196     waitForVideo('remote-view-2');
197   }
198
199   // Test that we can setup call with legacy settings.
200   function callWithLegacySdp() {
201     transformSdp = function(sdp) {
202       return removeBundle(useGice(useExternalSdes(sdp)));
203     };
204     transformCandidate = addGiceCredsToCandidate;
205     createConnections({
206       'mandatory': {'RtpDataChannels': true, 'DtlsSrtpKeyAgreement': false}
207     });
208     setupDataChannel({reliable: false});
209     navigator.webkitGetUserMedia({audio: true, video: true},
210         addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
211     waitForVideo('remote-view-1');
212     waitForVideo('remote-view-2');
213   }
214
215   // Test only a data channel.
216   function callWithDataOnly() {
217     createConnections({optional:[{RtpDataChannels: true}]});
218     setupDataChannel({reliable: false});
219     negotiate();
220   }
221
222   function callWithSctpDataOnly() {
223     createConnections({optional: [{DtlsSrtpKeyAgreement: true}]});
224     setupSctpDataChannel({reliable: true});
225     negotiate();
226   }
227
228   // Test call with audio, video and a data channel.
229   function callWithDataAndMedia() {
230     createConnections({optional:[{RtpDataChannels: true}]});
231     setupDataChannel({reliable: false});
232     navigator.webkitGetUserMedia({audio: true, video: true},
233       addStreamToBothConnectionsAndNegotiate,
234       printGetUserMediaError);
235     waitForVideo('remote-view-1');
236     waitForVideo('remote-view-2');
237   }
238
239   function callWithSctpDataAndMedia() {
240     createConnections({optional: [{DtlsSrtpKeyAgreement: true}]});
241     setupSctpDataChannel({reliable: true});
242     navigator.webkitGetUserMedia({audio: true, video: true},
243       addStreamToBothConnectionsAndNegotiate,
244       printGetUserMediaError);
245     waitForVideo('remote-view-1');
246     waitForVideo('remote-view-2');
247   }
248
249
250   // Test call with a data channel and later add audio and video.
251   function callWithDataAndLaterAddMedia() {
252     createConnections({optional:[{RtpDataChannels: true}]});
253     setupDataChannel({reliable: false});
254     negotiate();
255
256     // Set an event handler for when the data channel has been closed.
257     setAllEventsOccuredHandler(function() {
258       // When the video is flowing the test is done.
259       setAllEventsOccuredHandler(reportTestSuccess);
260       navigator.webkitGetUserMedia({audio: true, video: true},
261         addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
262       waitForVideo('remote-view-1');
263       waitForVideo('remote-view-2');
264     });
265   }
266
267   // Test that we can setup call and send DTMF.
268   function callAndSendDtmf(tones) {
269     createConnections(null);
270     navigator.webkitGetUserMedia({audio: true, video: true},
271       addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
272     var onCallEstablished = function() {
273       // Send DTMF tones.
274       var localAudioTrack = gLocalStream.getAudioTracks()[0];
275       var dtmfSender = gFirstConnection.createDTMFSender(localAudioTrack);
276       dtmfSender.ontonechange = onToneChange;
277       dtmfSender.insertDTMF(tones);
278       // Wait for the DTMF tones callback.
279       addExpectedEvent();
280       var waitDtmf = setInterval(function() {
281         if (gSentTones == tones) {
282           clearInterval(waitDtmf);
283           eventOccured();
284         }
285       }, 100);
286     }
287
288     // Do the DTMF test after we have received video.
289     detectVideoPlaying('remote-view-2', onCallEstablished);
290   }
291
292   function enableRemoteVideo(peerConnection, enabled) {
293     remoteStream = peerConnection.getRemoteStreams()[0];
294     remoteVideoTrack = remoteStream.getVideoTracks()[0];
295     remoteVideoTrack.enabled = enabled;
296   }
297
298   function enableRemoteAudio(peerConnection, enabled) {
299     remoteStream = peerConnection.getRemoteStreams()[0];
300     remoteAudioTrack = remoteStream.getAudioTracks()[0];
301     remoteAudioTrack.enabled = enabled;
302   }
303
304   function callAndEnsureAudioIsPlaying(beLenient, constraints) {
305     createConnections(null);
306     navigator.webkitGetUserMedia(constraints,
307       addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
308
309     // Wait until we have gathered samples and can conclude if audio is playing.
310     addExpectedEvent();
311     var onCallEstablished = function() {
312       // Gather 50 samples per second for 2 seconds.
313       gatherAudioLevelSamples(gSecondConnection, 100, 50, function(samples) {
314         verifyAudioIsPlaying(samples, beLenient);
315         eventOccured();
316       });
317     };
318
319     waitForConnectionToStabilize(gFirstConnection, onCallEstablished);
320   }
321
322   function callAndEnsureAudioTrackMutingWorks(beLenient) {
323     callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
324     setAllEventsOccuredHandler(function() {
325       // Call is up, now mute the track and check everything goes silent (give
326       // it a small delay though, we don't expect it to happen instantly).
327       enableRemoteAudio(gSecondConnection, false);
328
329       setTimeout(function() {
330         gatherAudioLevelSamples(gSecondConnection, 100, 50, function(samples) {
331           verifyIsSilent(samples);
332           reportTestSuccess();
333         });
334       }, 500);
335     });
336   }
337
338   function callAndEnsureAudioTrackUnmutingWorks(beLenient) {
339     callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
340     setAllEventsOccuredHandler(function() {
341       // Mute, wait a while, unmute, verify audio gets back up.
342       // (Also, ensure video muting doesn't affect audio).
343       enableRemoteAudio(gSecondConnection, false);
344       enableRemoteVideo(gSecondConnection, false);
345
346       setTimeout(function() {
347         enableRemoteAudio(gSecondConnection, true);
348       }, 500);
349
350       setTimeout(function() {
351         // Sample for four seconds here; it can take a bit of time for audio to
352         // get back up after the unmute.
353         gatherAudioLevelSamples(gSecondConnection, 200, 50, function(samples) {
354           verifyAudioIsPlaying(samples, beLenient);
355           reportTestSuccess();
356         });
357       }, 1500);
358     });
359   }
360
361   function callAndEnsureVideoTrackMutingWorks() {
362     createConnections(null);
363     navigator.webkitGetUserMedia({audio: true, video: true},
364       addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
365
366     addExpectedEvent();
367     detectVideoPlaying('remote-view-2', function() {
368       // Disable the receiver's remote media stream. Video should stop.
369       // (Also, ensure muting audio doesn't affect video).
370       enableRemoteVideo(gSecondConnection, false);
371       enableRemoteAudio(gSecondConnection, false);
372
373       detectVideoStopped('remote-view-2', function() {
374         // Video has stopped: unmute and succeed if it starts playing again.
375         enableRemoteVideo(gSecondConnection, true);
376         detectVideoPlaying('remote-view-2', eventOccured);
377       })
378     });
379   }
380
381   // Test call with a new Video MediaStream that has been created based on a
382   // stream generated by getUserMedia.
383   function callWithNewVideoMediaStream() {
384     createConnections(null);
385     navigator.webkitGetUserMedia({audio: true, video: true},
386         createNewVideoStreamAndAddToBothConnections, printGetUserMediaError);
387     waitForVideo('remote-view-1');
388     waitForVideo('remote-view-2');
389   }
390
391   // Test call with a new Video MediaStream that has been created based on a
392   // stream generated by getUserMedia. When Video is flowing, an audio track
393   // is added to the sent stream and the video track is removed. This
394   // is to test that adding and removing of remote tracks on an existing
395   // mediastream works.
396   function callWithNewVideoMediaStreamLaterSwitchToAudio() {
397     createConnections(null);
398     navigator.webkitGetUserMedia({audio: true, video: true},
399         createNewVideoStreamAndAddToBothConnections, printGetUserMediaError);
400
401     waitForVideo('remote-view-1');
402     waitForVideo('remote-view-2');
403
404     // Set an event handler for when video is playing.
405     setAllEventsOccuredHandler(function() {
406       // Add an audio track to the local stream and remove the video track and
407       // then renegotiate. But first - setup the expectations.
408       local_stream = gFirstConnection.getLocalStreams()[0];
409
410       remote_stream_1 = gFirstConnection.getRemoteStreams()[0];
411       // Add an expected event that onaddtrack will be called on the remote
412       // mediastream received on gFirstConnection when the audio track is
413       // received.
414       addExpectedEvent();
415       remote_stream_1.onaddtrack = function(){
416         assertEquals(remote_stream_1.getAudioTracks()[0].id,
417                      local_stream.getAudioTracks()[0].id);
418         eventOccured();
419       }
420
421       // Add an expectation that the received video track is removed from
422       // gFirstConnection.
423       addExpectedEvent();
424       remote_stream_1.onremovetrack = function() {
425         eventOccured();
426       }
427
428       // Add an expected event that onaddtrack will be called on the remote
429       // mediastream received on gSecondConnection when the audio track is
430       // received.
431       remote_stream_2 = gSecondConnection.getRemoteStreams()[0];
432       addExpectedEvent();
433       remote_stream_2.onaddtrack = function() {
434         assertEquals(remote_stream_2.getAudioTracks()[0].id,
435                      local_stream.getAudioTracks()[0].id);
436         eventOccured();
437       }
438
439       // Add an expectation that the received video track is removed from
440       // gSecondConnection.
441       addExpectedEvent();
442       remote_stream_2.onremovetrack = function() {
443         eventOccured();
444       }
445       // When all the above events have occurred- the test pass.
446       setAllEventsOccuredHandler(reportTestSuccess);
447
448       local_stream.addTrack(gLocalStream.getAudioTracks()[0]);
449       local_stream.removeTrack(local_stream.getVideoTracks()[0]);
450       negotiate();
451     });
452   }
453
454   // This function is used for setting up a test that:
455   // 1. Creates a data channel on |gFirstConnection| and sends data to
456   //    |gSecondConnection|.
457   // 2. When data is received on |gSecondConnection| a message
458   //    is sent to |gFirstConnection|.
459   // 3. When data is received on |gFirstConnection|, the data
460   //    channel is closed. The test passes when the state transition completes.
461   function setupDataChannel(params) {
462     var sendDataString = "send some text on a data channel."
463     firstDataChannel = gFirstConnection.createDataChannel(
464         "sendDataChannel", params);
465     assertEquals('connecting', firstDataChannel.readyState);
466
467     // When |firstDataChannel| transition to open state, send a text string.
468     firstDataChannel.onopen = function() {
469       assertEquals('open', firstDataChannel.readyState);
470       if (firstDataChannel.reliable) {
471         firstDataChannel.send(sendDataString);
472       } else {
473         sendDataRepeatedlyUntilClosed(firstDataChannel);
474       }
475     }
476
477     // When |firstDataChannel| receive a message, close the channel and
478     // initiate a new offer/answer exchange to complete the closure.
479     firstDataChannel.onmessage = function(event) {
480       assertEquals(event.data, sendDataString);
481       firstDataChannel.close();
482       negotiate();
483     }
484
485     // When |firstDataChannel| transition to closed state, the test pass.
486     addExpectedEvent();
487     firstDataChannel.onclose = function() {
488       assertEquals('closed', firstDataChannel.readyState);
489       eventOccured();
490     }
491
492     // Event handler for when |gSecondConnection| receive a new dataChannel.
493     gSecondConnection.ondatachannel = function (event) {
494       var secondDataChannel = event.channel;
495
496       // When |secondDataChannel| receive a message, send a message back.
497       secondDataChannel.onmessage = function(event) {
498         assertEquals(event.data, sendDataString);
499         console.log("gSecondConnection received data");
500         if (secondDataChannel.reliable) {
501           // If we're reliable we will just send one message over the channel,
502           // and therefore channel one's message handler cannot have shut us
503           // down already.
504           assertEquals('open', secondDataChannel.readyState);
505           secondDataChannel.send(sendDataString);
506         } else {
507           // If unreliable, this could be one in a series of messages and it
508           // is possible we already replied (which will close our channel).
509           sendDataRepeatedlyUntilClosed(secondDataChannel);
510         }
511       }
512     }
513
514     // Sends |sendDataString| on |dataChannel| every 200ms as long as
515     // |dataChannel| is open.
516     function sendDataRepeatedlyUntilClosed(dataChannel) {
517       var sendTimer = setInterval(function() {
518         if (dataChannel.readyState == 'open')
519           dataChannel.send(sendDataString);
520         else
521           clearInterval(sendTimer);
522       }, 200);
523     }
524   }
525
526   // SCTP data channel setup is slightly different then RTP based
527   // channels. Due to a bug in libjingle, we can't send data immediately
528   // after channel becomes open. So for that reason in SCTP,
529   // we are sending data from second channel, when ondatachannel event is
530   // received. So data flow happens 2 -> 1 -> 2.
531   function setupSctpDataChannel(params) {
532     var sendDataString = "send some text on a data channel."
533     firstDataChannel = gFirstConnection.createDataChannel(
534         "sendDataChannel", params);
535     assertEquals('connecting', firstDataChannel.readyState);
536
537     // When |firstDataChannel| transition to open state, send a text string.
538     firstDataChannel.onopen = function() {
539       assertEquals('open', firstDataChannel.readyState);
540     }
541
542     // When |firstDataChannel| receive a message, send message back.
543     // initiate a new offer/answer exchange to complete the closure.
544     firstDataChannel.onmessage = function(event) {
545       assertEquals('open', firstDataChannel.readyState);
546       assertEquals(event.data, sendDataString);
547       firstDataChannel.send(sendDataString);
548     }
549
550
551     // Event handler for when |gSecondConnection| receive a new dataChannel.
552     gSecondConnection.ondatachannel = function (event) {
553       var secondDataChannel = event.channel;
554       secondDataChannel.onopen = function() {
555         secondDataChannel.send(sendDataString);
556       }
557
558       // When |secondDataChannel| receive a message, close the channel and
559       // initiate a new offer/answer exchange to complete the closure.
560       secondDataChannel.onmessage = function(event) {
561         assertEquals(event.data, sendDataString);
562         assertEquals('open', secondDataChannel.readyState);
563         secondDataChannel.close();
564         negotiate();
565       }
566
567       // When |secondDataChannel| transition to closed state, the test pass.
568       addExpectedEvent();
569       secondDataChannel.onclose = function() {
570         assertEquals('closed', secondDataChannel.readyState);
571         eventOccured();
572       }
573     }
574   }
575
576   // Test call with a stream that has been created by getUserMedia, clone
577   // the stream to a cloned stream, send them via the same peer connection.
578   function addTwoMediaStreamsToOneConnection() {
579     createConnections(null);
580     navigator.webkitGetUserMedia({audio: true, video: true},
581         CloneStreamAndAddTwoStreamstoOneConnection, printGetUserMediaError);
582   }
583
584   function onToneChange(tone) {
585     gSentTones += tone.tone;
586   }
587
588   function createConnections(constraints) {
589     gFirstConnection = createConnection(constraints, 'remote-view-1');
590     assertEquals('stable', gFirstConnection.signalingState);
591
592     gSecondConnection = createConnection(constraints, 'remote-view-2');
593     assertEquals('stable', gSecondConnection.signalingState);
594   }
595
596   function createConnection(constraints, remoteView) {
597     var pc = new webkitRTCPeerConnection(null, constraints);
598     pc.onaddstream = function(event) {
599       onRemoteStream(event, remoteView);
600     }
601     return pc;
602   }
603
604   function displayAndRemember(localStream) {
605     var localStreamUrl = URL.createObjectURL(localStream);
606     $('local-view').src = localStreamUrl;
607
608     gLocalStream = localStream;
609   }
610
611   // Called if getUserMedia fails.
612   function printGetUserMediaError(error) {
613     var message = 'getUserMedia request unexpectedly failed:';
614     if (error.constraintName)
615       message += ' could not satisfy constraint ' + error.constraintName;
616     else
617       message += ' devices not working/user denied access.';
618     failTest(message);
619   }
620
621   // Called if getUserMedia succeeds and we want to send from both connections.
622   function addStreamToBothConnectionsAndNegotiate(localStream) {
623     displayAndRemember(localStream);
624     gFirstConnection.addStream(localStream);
625     gSecondConnection.addStream(localStream);
626     negotiate();
627   }
628
629   // Called if getUserMedia succeeds when we want to send from one connection.
630   function addStreamToTheFirstConnectionAndNegotiate(localStream) {
631     displayAndRemember(localStream);
632     gFirstConnection.addStream(localStream);
633     negotiate();
634   }
635
636   function verifyHasOneAudioAndVideoTrack(stream) {
637     assertEquals(1, stream.getAudioTracks().length);
638     assertEquals(1, stream.getVideoTracks().length);
639   }
640
641   // Called if getUserMedia succeeds, then clone the stream, send two streams
642   // from one peer connection.
643   function CloneStreamAndAddTwoStreamstoOneConnection(localStream) {
644     displayAndRemember(localStream);
645
646     var clonedStream = null;
647     if (typeof localStream.clone === "function") {
648       clonedStream = localStream.clone();
649     } else {
650       clonedStream = new webkitMediaStream(localStream);
651     }
652
653     gFirstConnection.addStream(localStream);
654     gFirstConnection.addStream(clonedStream);
655
656     // Verify the local streams are correct.
657     assertEquals(2, gFirstConnection.getLocalStreams().length);
658     verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[0]);
659     verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[1]);
660
661     // The remote side should receive two streams. After that, verify the
662     // remote side has the correct number of streams and tracks.
663     addExpectedEvent();
664     addExpectedEvent();
665     gSecondConnection.onaddstream = function(event) {
666       eventOccured();
667     }
668     setAllEventsOccuredHandler(function() {
669       // Negotiation complete, verify remote streams on the receiving side.
670       assertEquals(2, gSecondConnection.getRemoteStreams().length);
671       verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[0]);
672       verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[1]);
673
674       reportTestSuccess();
675     });
676
677     negotiate();
678   }
679
680   // Called if getUserMedia succeeds when we want to send a modified
681   // MediaStream. A new MediaStream is created and the video track from
682   // |localStream| is added.
683   function createNewVideoStreamAndAddToBothConnections(localStream) {
684     displayAndRemember(localStream);
685     var new_stream = new webkitMediaStream();
686     new_stream.addTrack(localStream.getVideoTracks()[0]);
687     gFirstConnection.addStream(new_stream);
688     gSecondConnection.addStream(new_stream);
689     negotiate();
690   }
691
692   function negotiate() {
693     negotiateBetween(gFirstConnection, gSecondConnection);
694   }
695
696   function negotiateBetween(caller, callee) {
697     console.log("Negotiating call...");
698     // Not stable = negotiation is ongoing. The behavior of re-negotiating while
699     // a negotiation is ongoing is more or less undefined, so avoid this.
700     if (caller.signalingState != 'stable' || callee.signalingState != 'stable')
701       throw 'You can only negotiate when the connection is stable!';
702
703     connectOnIceCandidate(caller, callee);
704
705     caller.createOffer(
706         function (offer) {
707           onOfferCreated(offer, caller, callee);
708         });
709   }
710
711   function onOfferCreated(offer, caller, callee) {
712     offer.sdp = maybeForceIsac16K(transformSdp(offer.sdp));
713     caller.setLocalDescription(offer, function() {
714       assertEquals('have-local-offer', caller.signalingState);
715       receiveOffer(offer.sdp, caller, callee);
716     }, onLocalDescriptionError);
717   }
718
719   function receiveOffer(offerSdp, caller, callee) {
720     console.log("Receiving offer...");
721     offerSdp = transformRemoteSdp(offerSdp);
722
723     var parsedOffer = new RTCSessionDescription({ type: 'offer',
724                                                   sdp: offerSdp });
725     callee.setRemoteDescription(parsedOffer, function() {},
726                                 onRemoteDescriptionError);
727     callee.createAnswer(function (answer) {
728                           onAnswerCreated(answer, caller, callee);
729                         });
730     assertEquals('have-remote-offer', callee.signalingState);
731   }
732
733   function removeMsid(offerSdp) {
734     offerSdp = offerSdp.replace(/a=msid-semantic.*\r\n/g, '');
735     offerSdp = offerSdp.replace('a=mid:audio\r\n', '');
736     offerSdp = offerSdp.replace('a=mid:video\r\n', '');
737     offerSdp = offerSdp.replace(/a=ssrc.*\r\n/g, '');
738     return offerSdp;
739   }
740
741   function removeVideoCodec(offerSdp) {
742     offerSdp = offerSdp.replace('a=rtpmap:100 VP8/90000\r\n',
743                                 'a=rtpmap:100 XVP8/90000\r\n');
744     return offerSdp;
745   }
746
747   function removeCrypto(offerSdp) {
748     offerSdp = offerSdp.replace(/a=crypto.*\r\n/g, 'a=Xcrypto\r\n');
749     offerSdp = offerSdp.replace(/a=fingerprint.*\r\n/g, '');
750     return offerSdp;
751   }
752
753   function addBandwithControl(offerSdp) {
754     offerSdp = offerSdp.replace('a=mid:audio\r\n', 'a=mid:audio\r\n'+
755                                 'b=AS:16\r\n');
756     offerSdp = offerSdp.replace('a=mid:video\r\n', 'a=mid:video\r\n'+
757                                 'b=AS:512\r\n');
758     return offerSdp;
759   }
760
761   function removeBundle(sdp) {
762     return sdp.replace(/a=group:BUNDLE .*\r\n/g, '');
763   }
764
765   function useGice(sdp) {
766     sdp = sdp.replace(/t=.*\r\n/g, function(subString) {
767       return subString + 'a=ice-options:google-ice\r\n';
768     });
769     sdp = sdp.replace(/a=ice-ufrag:.*\r\n/g,
770                       'a=ice-ufrag:' + EXTERNAL_GICE_UFRAG + '\r\n');
771     sdp = sdp.replace(/a=ice-pwd:.*\r\n/g,
772                       'a=ice-pwd:' + EXTERNAL_GICE_PWD + '\r\n');
773     return sdp;
774   }
775
776   function useExternalSdes(sdp) {
777     // Remove current crypto specification.
778     sdp = sdp.replace(/a=crypto.*\r\n/g, '');
779     sdp = sdp.replace(/a=fingerprint.*\r\n/g, '');
780     // Add external crypto.  This is not compatible with |removeMsid|.
781     sdp = sdp.replace(/a=mid:(\w+)\r\n/g, function(subString, group) {
782       return subString + EXTERNAL_SDES_LINES[group] + '\r\n';
783     });
784     return sdp;
785   }
786
787   function onAnswerCreated(answer, caller, callee) {
788     answer.sdp = maybeForceIsac16K(transformSdp(answer.sdp));
789     callee.setLocalDescription(answer,
790                                function () {
791                                  assertEquals('stable', callee.signalingState);
792                                },
793                                onLocalDescriptionError);
794     receiveAnswer(answer.sdp, caller);
795   }
796
797   function receiveAnswer(answerSdp, caller) {
798     console.log("Receiving answer...");
799     answerSdp = transformRemoteSdp(answerSdp);
800     var parsedAnswer = new RTCSessionDescription({ type: 'answer',
801                                                    sdp: answerSdp });
802     caller.setRemoteDescription(parsedAnswer,
803                                 function() {
804                                   assertEquals('stable', caller.signalingState);
805                                 },
806                                 onRemoteDescriptionError);
807   }
808
809   function connectOnIceCandidate(caller, callee) {
810     caller.onicecandidate = function(event) { onIceCandidate(event, callee); }
811     callee.onicecandidate = function(event) { onIceCandidate(event, caller); }
812   }
813
814   function addGiceCredsToCandidate(candidate) {
815     return candidate.trimRight() +
816         ' username ' + EXTERNAL_GICE_UFRAG + ' password ' + EXTERNAL_GICE_PWD;
817   }
818
819   function onIceCandidate(event, target) {
820     if (event.candidate) {
821       var candidate = new RTCIceCandidate(event.candidate);
822       candidate.candidate = transformCandidate(candidate.candidate);
823       target.addIceCandidate(candidate);
824     }
825   }
826
827   function onRemoteStream(e, target) {
828     console.log("Receiving remote stream...");
829     if (gTestWithoutMsid && e.stream.id != "default") {
830       failTest('a default remote stream was expected but instead ' +
831           e.stream.id + ' was received.');
832     }
833     gRemoteStreams[target] = e.stream;
834     var remoteStreamUrl = URL.createObjectURL(e.stream);
835     var remoteVideo = $(target);
836     remoteVideo.src = remoteStreamUrl;
837   }
838
839   </script>
840 </head>
841 <body>
842   <table border="0">
843     <tr>
844       <td>Local Preview</td>
845       <td>Remote Stream for Connection 1</td>
846       <td>Remote Stream for Connection 2</td>
847       <td>Remote Stream for Connection 3</td>
848       <td>Remote Stream for Connection 4</td>
849     </tr>
850     <tr>
851       <td><video width="320" height="240" id="local-view"
852           autoplay="autoplay"></video></td>
853       <td><video width="320" height="240" id="remote-view-1"
854           autoplay="autoplay"></video></td>
855       <td><video width="320" height="240" id="remote-view-2"
856           autoplay="autoplay"></video></td>
857       <td><video width="320" height="240" id="remote-view-3"
858           autoplay="autoplay"></video></td>
859       <td><video width="320" height="240" id="remote-view-4"
860           autoplay="autoplay"></video></td>
861       <!-- Canvases are named after their corresponding video elements. -->
862       <td><canvas width="320" height="240" id="remote-view-1-canvas"
863           style="display:none"></canvas></td>
864       <td><canvas width="320" height="240" id="remote-view-2-canvas"
865           style="display:none"></canvas></td>
866       <td><canvas width="320" height="240" id="remote-view-3-canvas"
867           style="display:none"></canvas></td>
868       <td><canvas width="320" height="240" id="remote-view-4-canvas"
869           style="display:none"></canvas></td>
870     </tr>
871   </table>
872 </body>
873 </html>