Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / content / test / data / media / peerconnection-call.html
blob3ee42ec5686a5f1d1a250736385dad0ffc5f0860
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 a call with a video track and that the remote peer
62 // receives black frames if the local video track is disabled.
63 function callAndDisableLocalVideo(constraints) {
64 createConnections(null);
65 navigator.webkitGetUserMedia(constraints,
66 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
67 detectVideoPlaying('remote-view-1',
68 function () {
69 assertEquals(gLocalStream.getVideoTracks().length, 1);
70 gLocalStream.getVideoTracks()[0].enabled = false;
71 waitForBlackVideo('remote-view-1');
72 });
75 // Test that we can setup call with an audio and video track and check that
76 // the video resolution is as expected.
77 function callAndExpectResolution(constraints,
78 expected_width,
79 expected_height) {
80 createConnections(null);
81 navigator.webkitGetUserMedia(constraints,
82 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
83 waitForVideoWithResolution('remote-view-1',
84 expected_width,
85 expected_height);
86 waitForVideoWithResolution('remote-view-2',
87 expected_width,
88 expected_height);
92 // First calls without streams on any connections, and then adds a stream
93 // to peer connection 1 which gets sent to peer connection 2. We must wait
94 // for the first negotiation to complete before starting the second one, which
95 // is why we wait until the connection is stable before re-negotiating.
96 function callEmptyThenAddOneStreamAndRenegotiate(constraints) {
97 createConnections(null);
98 negotiate();
99 waitForConnectionToStabilize(gFirstConnection, function() {
100 navigator.webkitGetUserMedia(constraints,
101 addStreamToTheFirstConnectionAndNegotiate, printGetUserMediaError);
102 // Only the first connection is sending here.
103 waitForVideo('remote-view-2');
107 // First makes a call between pc1 and pc2, and then makes a call between pc3
108 // and pc4. The stream sent from pc3 to pc4 is the stream received on pc1.
109 // The stream sent from pc4 to pc3 is cloned from the stream received on pc2
110 // to test that cloning of remote video tracks works as intended.
111 function callAndForwardRemoteStream(constraints) {
112 createConnections(null);
113 navigator.webkitGetUserMedia(constraints,
114 addStreamToBothConnectionsAndNegotiate,
115 printGetUserMediaError);
116 var gotRemoteStream1 = false;
117 var gotRemoteStream2 = false;
119 var onRemoteStream1 = function() {
120 gotRemoteStream1 = true;
121 maybeCallEstablished();
124 var onRemoteStream2 = function() {
125 gotRemoteStream2 = true;
126 maybeCallEstablished();
129 var maybeCallEstablished = function() {
130 if (gotRemoteStream1 && gotRemoteStream2) {
131 onCallEstablished();
135 var onCallEstablished = function() {
136 thirdConnection = createConnection(null, 'remote-view-3');
137 thirdConnection.addStream(gRemoteStreams['remote-view-1']);
139 fourthConnection = createConnection(null, 'remote-view-4');
140 fourthConnection.addStream(gRemoteStreams['remote-view-2'].clone());
142 negotiateBetween(thirdConnection, fourthConnection);
144 waitForVideo('remote-view-3');
145 waitForVideo('remote-view-4');
148 // Do the forwarding after we have received video.
149 detectVideoPlaying('remote-view-1', onRemoteStream1);
150 detectVideoPlaying('remote-view-2', onRemoteStream2);
153 // First makes a call between pc1 and pc2, and then construct a new media
154 // stream using the remote audio and video tracks, connect the new media
155 // stream to a video element. These operations should not crash Chrome.
156 function ConnectChromiumSinkToRemoteAudioTrack() {
157 createConnections(null);
158 navigator.webkitGetUserMedia({audio: true, video: true},
159 addStreamToBothConnectionsAndNegotiate,
160 printGetUserMediaError);
162 detectVideoPlaying('remote-view-2', function() {
163 // Construct a new media stream with remote tracks.
164 var newStream = new webkitMediaStream();
165 newStream.addTrack(
166 gSecondConnection.getRemoteStreams()[0].getAudioTracks()[0]);
167 newStream.addTrack(
168 gSecondConnection.getRemoteStreams()[0].getVideoTracks()[0]);
169 var videoElement = document.createElement('video');
171 // No crash for this operation.
172 videoElement.src = URL.createObjectURL(newStream);
173 waitForVideo('remote-view-2');
177 // Test that we can setup call with an audio and video track and
178 // simulate that the remote peer don't support MSID.
179 function callWithoutMsidAndBundle() {
180 createConnections(null);
181 transformSdp = removeBundle;
182 transformRemoteSdp = removeMsid;
183 gTestWithoutMsid = true;
184 navigator.webkitGetUserMedia({audio: true, video: true},
185 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
186 waitForVideo('remote-view-1');
187 waitForVideo('remote-view-2');
190 // Test that we can't setup a call with an unsupported video codec
191 function negotiateUnsupportedVideoCodec() {
192 createConnections(null);
193 transformSdp = removeVideoCodec;
195 onLocalDescriptionError = function(error) {
196 var expectedMsg = 'Failed to set local offer sdp:' +
197 ' Session error code: ERROR_CONTENT. Session error description:' +
198 ' Failed to set video receive codecs..';
199 assertEquals(expectedMsg, error);
200 reportTestSuccess();
202 navigator.webkitGetUserMedia({audio: true, video: true},
203 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
206 // Test that we can't setup a call if one peer does not support encryption
207 function negotiateNonCryptoCall() {
208 createConnections(null);
209 transformSdp = removeCrypto;
210 onLocalDescriptionError = function(error) {
211 var expectedMsg = 'Failed to set local offer sdp:' +
212 ' Called with SDP without DTLS fingerprint.';
214 assertEquals(expectedMsg, error);
215 reportTestSuccess();
217 navigator.webkitGetUserMedia({audio: true, video: true},
218 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
221 // Test that we can negotiate a call with an SDP offer that includes a
222 // b=AS:XX line to control audio and video bandwidth
223 function negotiateOfferWithBLine() {
224 createConnections(null);
225 transformSdp = addBandwithControl;
226 navigator.webkitGetUserMedia({audio: true, video: true},
227 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
228 waitForVideo('remote-view-1');
229 waitForVideo('remote-view-2');
232 // Test that we can setup call with legacy settings.
233 function callWithLegacySdp() {
234 transformSdp = function(sdp) {
235 return removeBundle(useGice(useExternalSdes(sdp)));
237 createConnections({
238 'mandatory': {'RtpDataChannels': true, 'DtlsSrtpKeyAgreement': false}
240 setupDataChannel({reliable: false});
241 navigator.webkitGetUserMedia({audio: true, video: true},
242 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
243 waitForVideo('remote-view-1');
244 waitForVideo('remote-view-2');
247 // Test only a data channel.
248 function callWithDataOnly() {
249 createConnections({optional:[{RtpDataChannels: true}]});
250 setupDataChannel({reliable: false});
251 negotiate();
254 function callWithSctpDataOnly() {
255 createConnections({optional: [{DtlsSrtpKeyAgreement: true}]});
256 setupSctpDataChannel({reliable: true});
257 negotiate();
260 // Test call with audio, video and a data channel.
261 function callWithDataAndMedia() {
262 createConnections({optional:[{RtpDataChannels: true}]});
263 setupDataChannel({reliable: false});
264 navigator.webkitGetUserMedia({audio: true, video: true},
265 addStreamToBothConnectionsAndNegotiate,
266 printGetUserMediaError);
267 waitForVideo('remote-view-1');
268 waitForVideo('remote-view-2');
271 function callWithSctpDataAndMedia() {
272 createConnections({optional: [{DtlsSrtpKeyAgreement: true}]});
273 setupSctpDataChannel({reliable: true});
274 navigator.webkitGetUserMedia({audio: true, video: true},
275 addStreamToBothConnectionsAndNegotiate,
276 printGetUserMediaError);
277 waitForVideo('remote-view-1');
278 waitForVideo('remote-view-2');
282 // Test call with a data channel and later add audio and video.
283 function callWithDataAndLaterAddMedia() {
284 createConnections({optional:[{RtpDataChannels: true}]});
285 setupDataChannel({reliable: false});
286 negotiate();
288 // Set an event handler for when the data channel has been closed.
289 setAllEventsOccuredHandler(function() {
290 // When the video is flowing the test is done.
291 setAllEventsOccuredHandler(reportTestSuccess);
292 navigator.webkitGetUserMedia({audio: true, video: true},
293 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
294 waitForVideo('remote-view-1');
295 waitForVideo('remote-view-2');
299 // Test that we can setup call and send DTMF.
300 function callAndSendDtmf(tones) {
301 createConnections(null);
302 navigator.webkitGetUserMedia({audio: true, video: true},
303 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
304 var onCallEstablished = function() {
305 // Send DTMF tones.
306 var localAudioTrack = gLocalStream.getAudioTracks()[0];
307 var dtmfSender = gFirstConnection.createDTMFSender(localAudioTrack);
308 dtmfSender.ontonechange = onToneChange;
309 dtmfSender.insertDTMF(tones);
310 // Wait for the DTMF tones callback.
311 addExpectedEvent();
312 var waitDtmf = setInterval(function() {
313 if (gSentTones == tones) {
314 clearInterval(waitDtmf);
315 eventOccured();
317 }, 100);
320 // Do the DTMF test after we have received video.
321 detectVideoPlaying('remote-view-2', onCallEstablished);
324 function testCreateOfferOptions() {
325 createConnections(null);
326 var offerOptions = {
327 'offerToReceiveAudio': false,
328 'offerToReceiveVideo': true
331 gFirstConnection.createOffer(
332 function(offer) {
333 assertEquals(-1, offer.sdp.search('m=audio'));
334 assertNotEquals(-1, offer.sdp.search('m=video'));
336 reportTestSuccess();
338 function(error) { failTest(error); },
339 offerOptions);
342 function callAndEnsureAudioIsPlaying(beLenient, constraints) {
343 createConnections(null);
345 // Add the local stream to gFirstConnection to play one-way audio.
346 navigator.webkitGetUserMedia(constraints,
347 addStreamToTheFirstConnectionAndNegotiate, printGetUserMediaError);
349 var onCallEstablished = function() {
350 ensureAudioPlaying(gSecondConnection, beLenient);
353 waitForConnectionToStabilize(gFirstConnection, onCallEstablished);
356 function enableRemoteVideo(peerConnection, enabled) {
357 remoteStream = peerConnection.getRemoteStreams()[0];
358 remoteStream.getVideoTracks()[0].enabled = enabled;
361 function enableRemoteAudio(peerConnection, enabled) {
362 remoteStream = peerConnection.getRemoteStreams()[0];
363 remoteStream.getAudioTracks()[0].enabled = enabled;
366 function enableLocalVideo(peerConnection, enabled) {
367 localStream = peerConnection.getLocalStreams()[0];
368 localStream.getVideoTracks()[0].enabled = enabled;
371 function enableLocalAudio(peerConnection, enabled) {
372 localStream = peerConnection.getLocalStreams()[0];
373 localStream.getAudioTracks()[0].enabled = enabled;
376 function callAndEnsureRemoteAudioTrackMutingWorks(beLenient) {
377 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
378 setAllEventsOccuredHandler(function() {
379 setAllEventsOccuredHandler(reportTestSuccess);
381 // Call is up, now mute the remote track and check we stop playing out
382 // audio (after a small delay, we don't expect it to happen instantly).
383 enableRemoteAudio(gSecondConnection, false);
384 ensureSilence(gSecondConnection);
388 function callAndEnsureLocalAudioTrackMutingWorks(beLenient) {
389 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
390 setAllEventsOccuredHandler(function() {
391 setAllEventsOccuredHandler(reportTestSuccess);
393 // Call is up, now mute the local track of the sending side and ensure
394 // the receiving side stops receiving audio.
395 enableLocalAudio(gFirstConnection, false);
396 ensureSilence(gSecondConnection);
400 function callAndEnsureAudioTrackUnmutingWorks(beLenient) {
401 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
402 setAllEventsOccuredHandler(function() {
403 setAllEventsOccuredHandler(reportTestSuccess);
405 // Mute, wait a while, unmute, verify audio gets back up.
406 // (Also, ensure video muting doesn't affect audio).
407 enableRemoteAudio(gSecondConnection, false);
408 enableRemoteVideo(gSecondConnection, false);
410 setTimeout(function() {
411 enableRemoteAudio(gSecondConnection, true);
412 }, 500);
414 setTimeout(function() {
415 ensureAudioPlaying(gSecondConnection, beLenient);
416 }, 1500);
420 function callAndEnsureLocalVideoMutingDoesntMuteAudio(beLenient) {
421 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
422 setAllEventsOccuredHandler(function() {
423 setAllEventsOccuredHandler(reportTestSuccess);
424 enableLocalVideo(gFirstConnection, false);
425 ensureAudioPlaying(gSecondConnection, beLenient);
429 function callAndEnsureRemoteVideoMutingDoesntMuteAudio(beLenient) {
430 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
431 setAllEventsOccuredHandler(function() {
432 setAllEventsOccuredHandler(reportTestSuccess);
433 enableRemoteVideo(gSecondConnection, false);
434 ensureAudioPlaying(gSecondConnection, beLenient);
438 function callAndEnsureVideoTrackMutingWorks() {
439 createConnections(null);
440 navigator.webkitGetUserMedia({audio: true, video: true},
441 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
443 addExpectedEvent();
444 detectVideoPlaying('remote-view-2', function() {
445 // Disable the receiver's remote media stream. Video should stop.
446 // (Also, ensure muting audio doesn't affect video).
447 enableRemoteVideo(gSecondConnection, false);
448 enableRemoteAudio(gSecondConnection, false);
450 detectVideoStopped('remote-view-2', function() {
451 // Video has stopped: unmute and succeed if it starts playing again.
452 enableRemoteVideo(gSecondConnection, true);
453 detectVideoPlaying('remote-view-2', eventOccured);
458 // Test call with a new Video MediaStream that has been created based on a
459 // stream generated by getUserMedia.
460 function callWithNewVideoMediaStream() {
461 createConnections(null);
462 navigator.webkitGetUserMedia({audio: true, video: true},
463 createNewVideoStreamAndAddToBothConnections, printGetUserMediaError);
464 waitForVideo('remote-view-1');
465 waitForVideo('remote-view-2');
468 // Test call with a new Video MediaStream that has been created based on a
469 // stream generated by getUserMedia. When Video is flowing, an audio track
470 // is added to the sent stream and the video track is removed. This
471 // is to test that adding and removing of remote tracks on an existing
472 // mediastream works.
473 function callWithNewVideoMediaStreamLaterSwitchToAudio() {
474 createConnections(null);
475 navigator.webkitGetUserMedia({audio: true, video: true},
476 createNewVideoStreamAndAddToBothConnections, printGetUserMediaError);
478 waitForVideo('remote-view-1');
479 waitForVideo('remote-view-2');
481 // Set an event handler for when video is playing.
482 setAllEventsOccuredHandler(function() {
483 // Add an audio track to the local stream and remove the video track and
484 // then renegotiate. But first - setup the expectations.
485 local_stream = gFirstConnection.getLocalStreams()[0];
487 remote_stream_1 = gFirstConnection.getRemoteStreams()[0];
488 // Add an expected event that onaddtrack will be called on the remote
489 // mediastream received on gFirstConnection when the audio track is
490 // received.
491 addExpectedEvent();
492 remote_stream_1.onaddtrack = function(){
493 assertEquals(remote_stream_1.getAudioTracks()[0].id,
494 local_stream.getAudioTracks()[0].id);
495 eventOccured();
498 // Add an expectation that the received video track is removed from
499 // gFirstConnection.
500 addExpectedEvent();
501 remote_stream_1.onremovetrack = function() {
502 eventOccured();
505 // Add an expected event that onaddtrack will be called on the remote
506 // mediastream received on gSecondConnection when the audio track is
507 // received.
508 remote_stream_2 = gSecondConnection.getRemoteStreams()[0];
509 addExpectedEvent();
510 remote_stream_2.onaddtrack = function() {
511 assertEquals(remote_stream_2.getAudioTracks()[0].id,
512 local_stream.getAudioTracks()[0].id);
513 eventOccured();
516 // Add an expectation that the received video track is removed from
517 // gSecondConnection.
518 addExpectedEvent();
519 remote_stream_2.onremovetrack = function() {
520 eventOccured();
522 // When all the above events have occurred- the test pass.
523 setAllEventsOccuredHandler(reportTestSuccess);
525 local_stream.addTrack(gLocalStream.getAudioTracks()[0]);
526 local_stream.removeTrack(local_stream.getVideoTracks()[0]);
527 negotiate();
531 // This function is used for setting up a test that:
532 // 1. Creates a data channel on |gFirstConnection| and sends data to
533 // |gSecondConnection|.
534 // 2. When data is received on |gSecondConnection| a message
535 // is sent to |gFirstConnection|.
536 // 3. When data is received on |gFirstConnection|, the data
537 // channel is closed. The test passes when the state transition completes.
538 function setupDataChannel(params) {
539 var sendDataString = "send some text on a data channel."
540 firstDataChannel = gFirstConnection.createDataChannel(
541 "sendDataChannel", params);
542 assertEquals('connecting', firstDataChannel.readyState);
544 // When |firstDataChannel| transition to open state, send a text string.
545 firstDataChannel.onopen = function() {
546 assertEquals('open', firstDataChannel.readyState);
547 if (firstDataChannel.reliable) {
548 firstDataChannel.send(sendDataString);
549 } else {
550 sendDataRepeatedlyUntilClosed(firstDataChannel);
554 // When |firstDataChannel| receive a message, close the channel and
555 // initiate a new offer/answer exchange to complete the closure.
556 firstDataChannel.onmessage = function(event) {
557 assertEquals(event.data, sendDataString);
558 firstDataChannel.close();
559 negotiate();
562 // When |firstDataChannel| transition to closed state, the test pass.
563 addExpectedEvent();
564 firstDataChannel.onclose = function() {
565 assertEquals('closed', firstDataChannel.readyState);
566 eventOccured();
569 // Event handler for when |gSecondConnection| receive a new dataChannel.
570 gSecondConnection.ondatachannel = function (event) {
571 var secondDataChannel = event.channel;
573 // When |secondDataChannel| receive a message, send a message back.
574 secondDataChannel.onmessage = function(event) {
575 assertEquals(event.data, sendDataString);
576 console.log("gSecondConnection received data");
577 if (secondDataChannel.reliable) {
578 // If we're reliable we will just send one message over the channel,
579 // and therefore channel one's message handler cannot have shut us
580 // down already.
581 assertEquals('open', secondDataChannel.readyState);
582 secondDataChannel.send(sendDataString);
583 } else {
584 // If unreliable, this could be one in a series of messages and it
585 // is possible we already replied (which will close our channel).
586 sendDataRepeatedlyUntilClosed(secondDataChannel);
591 // Sends |sendDataString| on |dataChannel| every 200ms as long as
592 // |dataChannel| is open.
593 function sendDataRepeatedlyUntilClosed(dataChannel) {
594 var sendTimer = setInterval(function() {
595 if (dataChannel.readyState == 'open')
596 dataChannel.send(sendDataString);
597 else
598 clearInterval(sendTimer);
599 }, 200);
603 // SCTP data channel setup is slightly different then RTP based
604 // channels. Due to a bug in libjingle, we can't send data immediately
605 // after channel becomes open. So for that reason in SCTP,
606 // we are sending data from second channel, when ondatachannel event is
607 // received. So data flow happens 2 -> 1 -> 2.
608 function setupSctpDataChannel(params) {
609 var sendDataString = "send some text on a data channel."
610 firstDataChannel = gFirstConnection.createDataChannel(
611 "sendDataChannel", params);
612 assertEquals('connecting', firstDataChannel.readyState);
614 // When |firstDataChannel| transition to open state, send a text string.
615 firstDataChannel.onopen = function() {
616 assertEquals('open', firstDataChannel.readyState);
619 // When |firstDataChannel| receive a message, send message back.
620 // initiate a new offer/answer exchange to complete the closure.
621 firstDataChannel.onmessage = function(event) {
622 assertEquals('open', firstDataChannel.readyState);
623 assertEquals(event.data, sendDataString);
624 firstDataChannel.send(sendDataString);
628 // Event handler for when |gSecondConnection| receive a new dataChannel.
629 gSecondConnection.ondatachannel = function (event) {
630 var secondDataChannel = event.channel;
631 secondDataChannel.onopen = function() {
632 secondDataChannel.send(sendDataString);
635 // When |secondDataChannel| receive a message, close the channel and
636 // initiate a new offer/answer exchange to complete the closure.
637 secondDataChannel.onmessage = function(event) {
638 assertEquals(event.data, sendDataString);
639 assertEquals('open', secondDataChannel.readyState);
640 secondDataChannel.close();
641 negotiate();
644 // When |secondDataChannel| transition to closed state, the test pass.
645 addExpectedEvent();
646 secondDataChannel.onclose = function() {
647 assertEquals('closed', secondDataChannel.readyState);
648 eventOccured();
653 // Test call with a stream that has been created by getUserMedia, clone
654 // the stream to a cloned stream, send them via the same peer connection.
655 function addTwoMediaStreamsToOneConnection() {
656 createConnections(null);
657 navigator.webkitGetUserMedia({audio: true, video: true},
658 CloneStreamAndAddTwoStreamstoOneConnection, printGetUserMediaError);
661 function onToneChange(tone) {
662 gSentTones += tone.tone;
665 function createConnections(constraints) {
666 gFirstConnection = createConnection(constraints, 'remote-view-1');
667 assertEquals('stable', gFirstConnection.signalingState);
669 gSecondConnection = createConnection(constraints, 'remote-view-2');
670 assertEquals('stable', gSecondConnection.signalingState);
673 function createConnection(constraints, remoteView) {
674 var pc = new webkitRTCPeerConnection(null, constraints);
675 pc.onaddstream = function(event) {
676 onRemoteStream(event, remoteView);
678 return pc;
681 function displayAndRemember(localStream) {
682 var localStreamUrl = URL.createObjectURL(localStream);
683 $('local-view').src = localStreamUrl;
685 gLocalStream = localStream;
688 // Called if getUserMedia fails.
689 function printGetUserMediaError(error) {
690 var message = 'getUserMedia request unexpectedly failed:';
691 if (error.constraintName)
692 message += ' could not satisfy constraint ' + error.constraintName;
693 else
694 message += ' devices not working/user denied access.';
695 failTest(message);
698 // Called if getUserMedia succeeds and we want to send from both connections.
699 function addStreamToBothConnectionsAndNegotiate(localStream) {
700 displayAndRemember(localStream);
701 gFirstConnection.addStream(localStream);
702 gSecondConnection.addStream(localStream);
703 negotiate();
706 // Called if getUserMedia succeeds when we want to send from one connection.
707 function addStreamToTheFirstConnectionAndNegotiate(localStream) {
708 displayAndRemember(localStream);
709 gFirstConnection.addStream(localStream);
710 negotiate();
713 function verifyHasOneAudioAndVideoTrack(stream) {
714 assertEquals(1, stream.getAudioTracks().length);
715 assertEquals(1, stream.getVideoTracks().length);
718 // Called if getUserMedia succeeds, then clone the stream, send two streams
719 // from one peer connection.
720 function CloneStreamAndAddTwoStreamstoOneConnection(localStream) {
721 displayAndRemember(localStream);
723 var clonedStream = null;
724 if (typeof localStream.clone === "function") {
725 clonedStream = localStream.clone();
726 } else {
727 clonedStream = new webkitMediaStream(localStream);
730 gFirstConnection.addStream(localStream);
731 gFirstConnection.addStream(clonedStream);
733 // Verify the local streams are correct.
734 assertEquals(2, gFirstConnection.getLocalStreams().length);
735 verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[0]);
736 verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[1]);
738 // The remote side should receive two streams. After that, verify the
739 // remote side has the correct number of streams and tracks.
740 addExpectedEvent();
741 addExpectedEvent();
742 gSecondConnection.onaddstream = function(event) {
743 eventOccured();
745 setAllEventsOccuredHandler(function() {
746 // Negotiation complete, verify remote streams on the receiving side.
747 assertEquals(2, gSecondConnection.getRemoteStreams().length);
748 verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[0]);
749 verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[1]);
751 reportTestSuccess();
754 negotiate();
757 // Called if getUserMedia succeeds when we want to send a modified
758 // MediaStream. A new MediaStream is created and the video track from
759 // |localStream| is added.
760 function createNewVideoStreamAndAddToBothConnections(localStream) {
761 displayAndRemember(localStream);
762 var new_stream = new webkitMediaStream();
763 new_stream.addTrack(localStream.getVideoTracks()[0]);
764 gFirstConnection.addStream(new_stream);
765 gSecondConnection.addStream(new_stream);
766 negotiate();
769 function negotiate() {
770 negotiateBetween(gFirstConnection, gSecondConnection);
773 function negotiateBetween(caller, callee) {
774 console.log("Negotiating call...");
775 // Not stable = negotiation is ongoing. The behavior of re-negotiating while
776 // a negotiation is ongoing is more or less undefined, so avoid this.
777 if (caller.signalingState != 'stable' || callee.signalingState != 'stable')
778 throw 'You can only negotiate when the connection is stable!';
780 connectOnIceCandidate(caller, callee);
782 caller.createOffer(
783 function (offer) {
784 onOfferCreated(offer, caller, callee);
788 function onOfferCreated(offer, caller, callee) {
789 offer.sdp = maybeForceIsac16K(transformSdp(offer.sdp));
790 caller.setLocalDescription(offer, function() {
791 assertEquals('have-local-offer', caller.signalingState);
792 receiveOffer(offer.sdp, caller, callee);
793 }, onLocalDescriptionError);
796 function receiveOffer(offerSdp, caller, callee) {
797 console.log("Receiving offer...");
798 offerSdp = transformRemoteSdp(offerSdp);
800 var parsedOffer = new RTCSessionDescription({ type: 'offer',
801 sdp: offerSdp });
802 callee.setRemoteDescription(parsedOffer, function() {},
803 onRemoteDescriptionError);
804 callee.createAnswer(function (answer) {
805 onAnswerCreated(answer, caller, callee);
807 assertEquals('have-remote-offer', callee.signalingState);
810 function removeMsid(offerSdp) {
811 offerSdp = offerSdp.replace(/a=msid-semantic.*\r\n/g, '');
812 offerSdp = offerSdp.replace('a=mid:audio\r\n', '');
813 offerSdp = offerSdp.replace('a=mid:video\r\n', '');
814 offerSdp = offerSdp.replace(/a=ssrc.*\r\n/g, '');
815 return offerSdp;
818 function removeVideoCodec(offerSdp) {
819 offerSdp = offerSdp.replace('a=rtpmap:100 VP8/90000\r\n',
820 'a=rtpmap:100 XVP8/90000\r\n');
821 return offerSdp;
824 function removeCrypto(offerSdp) {
825 offerSdp = offerSdp.replace(/a=crypto.*\r\n/g, 'a=Xcrypto\r\n');
826 offerSdp = offerSdp.replace(/a=fingerprint.*\r\n/g, '');
827 return offerSdp;
830 function addBandwithControl(offerSdp) {
831 offerSdp = offerSdp.replace('a=mid:audio\r\n', 'a=mid:audio\r\n'+
832 'b=AS:16\r\n');
833 offerSdp = offerSdp.replace('a=mid:video\r\n', 'a=mid:video\r\n'+
834 'b=AS:512\r\n');
835 return offerSdp;
838 function removeBundle(sdp) {
839 return sdp.replace(/a=group:BUNDLE .*\r\n/g, '');
842 function useGice(sdp) {
843 sdp = sdp.replace(/t=.*\r\n/g, function(subString) {
844 return subString + 'a=ice-options:google-ice\r\n';
846 return sdp;
849 function useExternalSdes(sdp) {
850 // Remove current crypto specification.
851 sdp = sdp.replace(/a=crypto.*\r\n/g, '');
852 sdp = sdp.replace(/a=fingerprint.*\r\n/g, '');
853 // Add external crypto. This is not compatible with |removeMsid|.
854 sdp = sdp.replace(/a=mid:(\w+)\r\n/g, function(subString, group) {
855 return subString + EXTERNAL_SDES_LINES[group] + '\r\n';
857 return sdp;
860 function onAnswerCreated(answer, caller, callee) {
861 answer.sdp = maybeForceIsac16K(transformSdp(answer.sdp));
862 callee.setLocalDescription(answer,
863 function () {
864 assertEquals('stable', callee.signalingState);
866 onLocalDescriptionError);
867 receiveAnswer(answer.sdp, caller);
870 function receiveAnswer(answerSdp, caller) {
871 console.log("Receiving answer...");
872 answerSdp = transformRemoteSdp(answerSdp);
873 var parsedAnswer = new RTCSessionDescription({ type: 'answer',
874 sdp: answerSdp });
875 caller.setRemoteDescription(parsedAnswer,
876 function() {
877 assertEquals('stable', caller.signalingState);
879 onRemoteDescriptionError);
882 function connectOnIceCandidate(caller, callee) {
883 caller.onicecandidate = function(event) { onIceCandidate(event, callee); }
884 callee.onicecandidate = function(event) { onIceCandidate(event, caller); }
887 function onIceCandidate(event, target) {
888 if (event.candidate) {
889 var candidate = new RTCIceCandidate(event.candidate);
890 target.addIceCandidate(candidate);
894 function onRemoteStream(e, target) {
895 console.log("Receiving remote stream...");
896 if (gTestWithoutMsid && e.stream.id != "default") {
897 failTest('a default remote stream was expected but instead ' +
898 e.stream.id + ' was received.');
900 gRemoteStreams[target] = e.stream;
901 var remoteStreamUrl = URL.createObjectURL(e.stream);
902 var remoteVideo = $(target);
903 remoteVideo.src = remoteStreamUrl;
906 </script>
907 </head>
908 <body>
909 <table border="0">
910 <tr>
911 <td>Local Preview</td>
912 <td>Remote Stream for Connection 1</td>
913 <td>Remote Stream for Connection 2</td>
914 <td>Remote Stream for Connection 3</td>
915 <td>Remote Stream for Connection 4</td>
916 </tr>
917 <tr>
918 <td><video width="320" height="240" id="local-view" autoplay muted>
919 </video></td>
920 <td><video width="320" height="240" id="remote-view-1" autoplay>
921 </video></td>
922 <td><video width="320" height="240" id="remote-view-2" autoplay>
923 </video></td>
924 <td><video width="320" height="240" id="remote-view-3" autoplay>
925 </video></td>
926 <td><video width="320" height="240" id="remote-view-4" autoplay>
927 </video></td>
928 <!-- Canvases are named after their corresponding video elements. -->
929 <td><canvas width="320" height="240" id="remote-view-1-canvas"
930 style="display:none"></canvas></td>
931 <td><canvas width="320" height="240" id="remote-view-2-canvas"
932 style="display:none"></canvas></td>
933 <td><canvas width="320" height="240" id="remote-view-3-canvas"
934 style="display:none"></canvas></td>
935 <td><canvas width="320" height="240" id="remote-view-4-canvas"
936 style="display:none"></canvas></td>
937 </tr>
938 </table>
939 </body>
940 </html>