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