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">
7 return document
.getElementById(id
);
10 window
.onerror = function(errorMsg
, url
, lineNumber
, column
, errorObj
) {
11 failTest('Error: ' + errorMsg
+ '\nScript: ' + url
+
12 '\nLine: ' + lineNumber
+ '\nColumn: ' + column
+
13 '\nStackTrace: ' + errorObj
);
16 var gFirstConnection
= null;
17 var gSecondConnection
= null;
18 var gTestWithoutMsid
= false;
19 var gLocalStream
= null;
22 var gRemoteStreams
= {};
24 // Default transform functions, overridden by some test cases.
25 var transformSdp = function(sdp
) { return sdp
; };
26 var transformRemoteSdp = function(sdp
) { return sdp
; };
27 var onLocalDescriptionError = function(error
) { failTest(error
); };
28 var onRemoteDescriptionError = function(error
) { failTest(error
); };
30 // When using external SDES, the crypto key is chosen by javascript.
31 var EXTERNAL_SDES_LINES
= {
32 'audio': 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' +
33 'inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR',
34 'video': 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' +
35 'inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj',
36 'data': 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' +
37 'inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj'
40 setAllEventsOccuredHandler(reportTestSuccess
);
42 // Test that we can setup a call with an audio and video track (must request
43 // video in this call since we expect video to be playing).
44 function call(constraints
) {
45 createConnections(null);
46 navigator
.webkitGetUserMedia(constraints
,
47 addStreamToBothConnectionsAndNegotiate
, printGetUserMediaError
);
48 waitForVideo('remote-view-1');
49 waitForVideo('remote-view-2');
52 // Test that we can setup a call with a video track and that the remote peer
53 // receives black frames if the local video track is disabled.
54 function callAndDisableLocalVideo(constraints
) {
55 createConnections(null);
56 navigator
.webkitGetUserMedia(constraints
,
57 addStreamToBothConnectionsAndNegotiate
, printGetUserMediaError
);
58 detectVideoPlaying('remote-view-1',
60 assertEquals(gLocalStream
.getVideoTracks().length
, 1);
61 gLocalStream
.getVideoTracks()[0].enabled
= false;
62 waitForBlackVideo('remote-view-1');
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
,
71 createConnections(null);
72 navigator
.webkitGetUserMedia(constraints
,
73 addStreamToBothConnectionsAndNegotiate
, printGetUserMediaError
);
74 waitForVideoWithResolution('remote-view-1',
77 waitForVideoWithResolution('remote-view-2',
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);
90 waitForConnectionToStabilize(gFirstConnection
, function() {
91 navigator
.webkitGetUserMedia(constraints
,
92 addStreamToTheFirstConnectionAndNegotiate
, printGetUserMediaError
);
93 // Only the first connection is sending here.
94 waitForVideo('remote-view-2');
98 // The second set of constraints should request video (e.g. video:true) since
99 // we expect video to be playing after the second renegotiation.
100 function callAndRenegotiateToVideo(constraints
, renegotiationConstraints
) {
101 createConnections(null);
102 navigator
.webkitGetUserMedia(constraints
,
103 addStreamToBothConnectionsAndNegotiate
, printGetUserMediaError
);
105 waitForConnectionToStabilize(gFirstConnection
, function() {
106 gFirstConnection
.removeStream(gLocalStream
);
107 gSecondConnection
.removeStream(gLocalStream
);
109 navigator
.webkitGetUserMedia(renegotiationConstraints
,
110 addStreamToBothConnectionsAndNegotiate
, printGetUserMediaError
);
111 waitForVideo('remote-view-1');
112 waitForVideo('remote-view-2');
116 // The second set of constraints should request audio (e.g. audio:true) since
117 // we expect audio to be playing after the second renegotiation.
118 function callAndRenegotiateToAudio(constraints
, renegotiationConstraints
) {
119 createConnections(null);
120 navigator
.webkitGetUserMedia(constraints
,
121 addStreamToBothConnectionsAndNegotiate
, printGetUserMediaError
);
123 waitForConnectionToStabilize(gFirstConnection
, function() {
124 gFirstConnection
.removeStream(gLocalStream
);
125 gSecondConnection
.removeStream(gLocalStream
);
127 navigator
.webkitGetUserMedia(renegotiationConstraints
,
128 addStreamToTheFirstConnectionAndNegotiate
, printGetUserMediaError
);
130 var onCallEstablished = function() {
131 ensureAudioPlaying(gSecondConnection
);
134 waitForConnectionToStabilize(gFirstConnection
, onCallEstablished
);
138 // First makes a call between pc1 and pc2 where a stream is sent from pc1 to
139 // pc2. The stream sent from pc1 to pc2 is cloned from the stream received on
140 // pc2 to test that cloning of remote video tracks works as intended and is
142 function callAndForwardRemoteStream(constraints
) {
143 createConnections(null);
144 navigator
.webkitGetUserMedia(constraints
,
145 addStreamToTheFirstConnectionAndNegotiate
,
146 printGetUserMediaError
);
147 var onRemoteStream2 = function() {
148 // Video has been detected to be playing in pc2. Clone the received
149 // stream and send it back to pc1.
150 gSecondConnection
.addStream(gRemoteStreams
['remote-view-2'].clone());
154 // Wait for remove video to be playing in pc2. Once video is playing,
155 // forward the remove stream from pc2 to pc1.
156 detectVideoPlaying('remote-view-2', onRemoteStream2
);
158 // Wait for video to be forwarded back to connection 1.
159 waitForVideo('remote-view-1');
162 // First makes a call between pc1 and pc2, and then construct a new media
163 // stream using the remote audio and video tracks, connect the new media
164 // stream to a video element. These operations should not crash Chrome.
165 function ConnectChromiumSinkToRemoteAudioTrack() {
166 createConnections(null);
167 navigator
.webkitGetUserMedia({audio
: true, video
: true},
168 addStreamToBothConnectionsAndNegotiate
,
169 printGetUserMediaError
);
171 detectVideoPlaying('remote-view-2', function() {
172 // Construct a new media stream with remote tracks.
173 var newStream
= new webkitMediaStream();
175 gSecondConnection
.getRemoteStreams()[0].getAudioTracks()[0]);
177 gSecondConnection
.getRemoteStreams()[0].getVideoTracks()[0]);
178 var videoElement
= document
.createElement('video');
180 // No crash for this operation.
181 videoElement
.src
= URL
.createObjectURL(newStream
);
182 waitForVideo('remote-view-2');
186 // Test that we can setup call with an audio and video track and
187 // simulate that the remote peer don't support MSID.
188 function callWithoutMsidAndBundle() {
189 createConnections(null);
190 transformSdp
= removeBundle
;
191 transformRemoteSdp
= removeMsid
;
192 gTestWithoutMsid
= true;
193 navigator
.webkitGetUserMedia({audio
: true, video
: true},
194 addStreamToBothConnectionsAndNegotiate
, printGetUserMediaError
);
195 waitForVideo('remote-view-1');
196 waitForVideo('remote-view-2');
199 // Test that we can't setup a call with an unsupported video codec
200 function negotiateUnsupportedVideoCodec() {
201 createConnections(null);
202 transformSdp
= removeVideoCodec
;
204 onLocalDescriptionError = function(error
) {
205 var expectedMsg
= 'Failed to set local offer sdp:' +
206 ' Session error code: ERROR_CONTENT. Session error description:' +
207 ' Failed to set video receive codecs..';
208 assertEquals(expectedMsg
, error
);
211 navigator
.webkitGetUserMedia({audio
: true, video
: true},
212 addStreamToBothConnectionsAndNegotiate
, printGetUserMediaError
);
215 // Test that we can't setup a call if one peer does not support encryption
216 function negotiateNonCryptoCall() {
217 createConnections(null);
218 transformSdp
= removeCrypto
;
219 onLocalDescriptionError = function(error
) {
220 var expectedMsg
= 'Failed to set local offer sdp:' +
221 ' Called with SDP without DTLS fingerprint.';
223 assertEquals(expectedMsg
, error
);
226 navigator
.webkitGetUserMedia({audio
: true, video
: true},
227 addStreamToBothConnectionsAndNegotiate
, printGetUserMediaError
);
230 // Test that we can negotiate a call with an SDP offer that includes a
231 // b=AS:XX line to control audio and video bandwidth
232 function negotiateOfferWithBLine() {
233 createConnections(null);
234 transformSdp
= addBandwithControl
;
235 navigator
.webkitGetUserMedia({audio
: true, video
: true},
236 addStreamToBothConnectionsAndNegotiate
, printGetUserMediaError
);
237 waitForVideo('remote-view-1');
238 waitForVideo('remote-view-2');
241 // Test that we can setup call with legacy settings.
242 function callWithLegacySdp() {
243 transformSdp = function(sdp
) {
244 return removeBundle(useGice(useExternalSdes(sdp
)));
247 'mandatory': {'RtpDataChannels': true, 'DtlsSrtpKeyAgreement': false}
249 setupDataChannel({reliable
: false});
250 navigator
.webkitGetUserMedia({audio
: true, video
: true},
251 addStreamToBothConnectionsAndNegotiate
, printGetUserMediaError
);
252 waitForVideo('remote-view-1');
253 waitForVideo('remote-view-2');
256 // Test only a data channel.
257 function callWithDataOnly() {
258 createConnections({optional
:[{RtpDataChannels
: true}]});
259 setupDataChannel({reliable
: false});
263 function callWithSctpDataOnly() {
264 createConnections({optional
: [{DtlsSrtpKeyAgreement
: true}]});
265 setupSctpDataChannel({reliable
: true});
269 // Test call with audio, video and a data channel.
270 function callWithDataAndMedia() {
271 createConnections({optional
:[{RtpDataChannels
: true}]});
272 setupDataChannel({reliable
: false});
273 navigator
.webkitGetUserMedia({audio
: true, video
: true},
274 addStreamToBothConnectionsAndNegotiate
,
275 printGetUserMediaError
);
276 waitForVideo('remote-view-1');
277 waitForVideo('remote-view-2');
280 function callWithSctpDataAndMedia() {
281 createConnections({optional
: [{DtlsSrtpKeyAgreement
: true}]});
282 setupSctpDataChannel({reliable
: true});
283 navigator
.webkitGetUserMedia({audio
: true, video
: true},
284 addStreamToBothConnectionsAndNegotiate
,
285 printGetUserMediaError
);
286 waitForVideo('remote-view-1');
287 waitForVideo('remote-view-2');
290 // Test call with a data channel and later add audio and video.
291 function callWithDataAndLaterAddMedia() {
292 createConnections({optional
:[{RtpDataChannels
: true}]});
293 setupDataChannel({reliable
: false});
296 // Set an event handler for when the data channel has been closed.
297 setAllEventsOccuredHandler(function() {
298 // When the video is flowing the test is done.
299 setAllEventsOccuredHandler(reportTestSuccess
);
300 navigator
.webkitGetUserMedia({audio
: true, video
: true},
301 addStreamToBothConnectionsAndNegotiate
, printGetUserMediaError
);
302 waitForVideo('remote-view-1');
303 waitForVideo('remote-view-2');
307 // Test that we can setup call and send DTMF.
308 function callAndSendDtmf(tones
) {
309 createConnections(null);
310 navigator
.webkitGetUserMedia({audio
: true, video
: true},
311 addStreamToBothConnectionsAndNegotiate
, printGetUserMediaError
);
312 var onCallEstablished = function() {
313 // Send DTMF tones. Allocate the sender in the window to keep it from
314 // being garbage collected. https://crbug.com/486654.
315 var localAudioTrack
= gLocalStream
.getAudioTracks()[0];
316 window
.dtmfSender
= gFirstConnection
.createDTMFSender(localAudioTrack
);
317 window
.dtmfSender
.ontonechange
= onToneChange
;
318 window
.dtmfSender
.insertDTMF(tones
);
319 // Wait for the DTMF tones callback.
321 var waitDtmf
= setInterval(function() {
322 if (gSentTones
== tones
) {
323 clearInterval(waitDtmf
);
329 // Do the DTMF test after we have received video.
330 detectVideoPlaying('remote-view-2', onCallEstablished
);
333 function testCreateOfferOptions() {
334 createConnections(null);
336 'offerToReceiveAudio': false,
337 'offerToReceiveVideo': true
340 gFirstConnection
.createOffer(
342 assertEquals(-1, offer
.sdp
.search('m=audio'));
343 assertNotEquals(-1, offer
.sdp
.search('m=video'));
347 function(error
) { failTest(error
); },
351 function callAndEnsureAudioIsPlaying(constraints
) {
352 createConnections(null);
354 // Add the local stream to gFirstConnection to play one-way audio.
355 navigator
.webkitGetUserMedia(constraints
,
356 addStreamToTheFirstConnectionAndNegotiate
, printGetUserMediaError
);
358 var onCallEstablished = function() {
359 ensureAudioPlaying(gSecondConnection
);
362 waitForConnectionToStabilize(gFirstConnection
, onCallEstablished
);
365 function callWithIsac16KAndEnsureAudioIsPlaying(constraints
) {
366 transformSdp = function(sdp
) {
367 sdp
= sdp
.replace(/m=audio (\d+) RTP\/SAVPF.*\r\n/g,
368 'm=audio $1 RTP/SAVPF 103 126\r\n');
369 sdp
= sdp
.replace('a=fmtp:111 minptime=10', 'a=fmtp:103 minptime=10');
370 if (sdp
.search('a=rtpmap:103 ISAC/16000') == -1)
371 failTest('Missing iSAC 16K codec on Android; cannot force codec.');
375 callAndEnsureAudioIsPlaying(constraints
);
378 function enableRemoteVideo(peerConnection
, enabled
) {
379 remoteStream
= peerConnection
.getRemoteStreams()[0];
380 remoteStream
.getVideoTracks()[0].enabled
= enabled
;
383 function enableRemoteAudio(peerConnection
, enabled
) {
384 remoteStream
= peerConnection
.getRemoteStreams()[0];
385 remoteStream
.getAudioTracks()[0].enabled
= enabled
;
388 function enableLocalVideo(peerConnection
, enabled
) {
389 localStream
= peerConnection
.getLocalStreams()[0];
390 localStream
.getVideoTracks()[0].enabled
= enabled
;
393 function enableLocalAudio(peerConnection
, enabled
) {
394 localStream
= peerConnection
.getLocalStreams()[0];
395 localStream
.getAudioTracks()[0].enabled
= enabled
;
398 function callAndEnsureRemoteAudioTrackMutingWorks() {
399 callAndEnsureAudioIsPlaying({audio
: true, video
: true});
400 setAllEventsOccuredHandler(function() {
401 setAllEventsOccuredHandler(reportTestSuccess
);
403 // Call is up, now mute the remote track and check we stop playing out
404 // audio (after a small delay, we don't expect it to happen instantly).
405 enableRemoteAudio(gSecondConnection
, false);
406 ensureSilence(gSecondConnection
);
410 function callAndEnsureLocalAudioTrackMutingWorks() {
411 callAndEnsureAudioIsPlaying({audio
: true, video
: true});
412 setAllEventsOccuredHandler(function() {
413 setAllEventsOccuredHandler(reportTestSuccess
);
415 // Call is up, now mute the local track of the sending side and ensure
416 // the receiving side stops receiving audio.
417 enableLocalAudio(gFirstConnection
, false);
418 ensureSilence(gSecondConnection
);
422 function callAndEnsureAudioTrackUnmutingWorks() {
423 callAndEnsureAudioIsPlaying({audio
: true, video
: true});
424 setAllEventsOccuredHandler(function() {
425 setAllEventsOccuredHandler(reportTestSuccess
);
427 // Mute, wait a while, unmute, verify audio gets back up.
428 // (Also, ensure video muting doesn't affect audio).
429 enableRemoteAudio(gSecondConnection
, false);
430 enableRemoteVideo(gSecondConnection
, false);
432 setTimeout(function() {
433 enableRemoteAudio(gSecondConnection
, true);
436 setTimeout(function() {
437 ensureAudioPlaying(gSecondConnection
);
442 function callAndEnsureLocalVideoMutingDoesntMuteAudio() {
443 callAndEnsureAudioIsPlaying({audio
: true, video
: true});
444 setAllEventsOccuredHandler(function() {
445 setAllEventsOccuredHandler(reportTestSuccess
);
446 enableLocalVideo(gFirstConnection
, false);
447 ensureAudioPlaying(gSecondConnection
);
451 function callAndEnsureRemoteVideoMutingDoesntMuteAudio() {
452 callAndEnsureAudioIsPlaying({audio
: true, video
: true});
453 setAllEventsOccuredHandler(function() {
454 setAllEventsOccuredHandler(reportTestSuccess
);
455 enableRemoteVideo(gSecondConnection
, false);
456 ensureAudioPlaying(gSecondConnection
);
460 function callAndEnsureVideoTrackMutingWorks() {
461 createConnections(null);
462 navigator
.webkitGetUserMedia({audio
: true, video
: true},
463 addStreamToBothConnectionsAndNegotiate
, printGetUserMediaError
);
466 detectVideoPlaying('remote-view-2', function() {
467 // Disable the receiver's remote media stream. Video should stop.
468 // (Also, ensure muting audio doesn't affect video).
469 enableRemoteVideo(gSecondConnection
, false);
470 enableRemoteAudio(gSecondConnection
, false);
472 detectVideoStopped('remote-view-2', function() {
473 // Video has stopped: unmute and succeed if it starts playing again.
474 enableRemoteVideo(gSecondConnection
, true);
475 detectVideoPlaying('remote-view-2', eventOccured
);
480 // Test call with a new Video MediaStream that has been created based on a
481 // stream generated by getUserMedia.
482 function callWithNewVideoMediaStream() {
483 createConnections(null);
484 navigator
.webkitGetUserMedia({audio
: true, video
: true},
485 createNewVideoStreamAndAddToBothConnections
, printGetUserMediaError
);
486 waitForVideo('remote-view-1');
487 waitForVideo('remote-view-2');
490 // Test call with a new Video MediaStream that has been created based on a
491 // stream generated by getUserMedia. When Video is flowing, an audio track
492 // is added to the sent stream and the video track is removed. This
493 // is to test that adding and removing of remote tracks on an existing
494 // mediastream works.
495 function callWithNewVideoMediaStreamLaterSwitchToAudio() {
496 createConnections(null);
497 navigator
.webkitGetUserMedia({audio
: true, video
: true},
498 createNewVideoStreamAndAddToBothConnections
, printGetUserMediaError
);
500 waitForVideo('remote-view-1');
501 waitForVideo('remote-view-2');
503 // Set an event handler for when video is playing.
504 setAllEventsOccuredHandler(function() {
505 // Add an audio track to the local stream and remove the video track and
506 // then renegotiate. But first - setup the expectations.
507 var localStream
= gFirstConnection
.getLocalStreams()[0];
508 var remoteStream1
= gFirstConnection
.getRemoteStreams()[0];
510 // Add an expected event that onaddtrack will be called on the remote
511 // mediastream received on gFirstConnection when the audio track is
514 remoteStream1
.onaddtrack = function(){
515 assertEquals(remoteStream1
.getAudioTracks()[0].id
,
516 localStream
.getAudioTracks()[0].id
);
520 // Add an expectation that the received video track is removed from
523 remoteStream1
.onremovetrack = function() {
527 // Add an expected event that onaddtrack will be called on the remote
528 // mediastream received on gSecondConnection when the audio track is
530 remoteStream2
= gSecondConnection
.getRemoteStreams()[0];
532 remoteStream2
.onaddtrack = function() {
533 assertEquals(remoteStream2
.getAudioTracks()[0].id
,
534 localStream
.getAudioTracks()[0].id
);
538 // Add an expectation that the received video track is removed from
539 // gSecondConnection.
541 remoteStream2
.onremovetrack = function() {
544 // When all the above events have occurred- the test pass.
545 setAllEventsOccuredHandler(reportTestSuccess
);
547 localStream
.addTrack(gLocalStream
.getAudioTracks()[0]);
548 localStream
.removeTrack(localStream
.getVideoTracks()[0]);
553 // Loads this page inside itself using an iframe, and ensures we can make a
554 // successful getUserMedia + peerconnection call inside the iframe.
555 function callInsideIframe(constraints
) {
556 runInsideIframe(function(iframe
) {
557 // Run a regular webrtc call inside the iframe.
558 iframe
.contentWindow
.call(constraints
);
562 // Func should accept an iframe as its first argument.
563 function runInsideIframe(func
) {
564 var iframe
= document
.createElement('iframe');
565 document
.body
.appendChild(iframe
);
566 iframe
.onload
= onIframeLoaded
;
567 iframe
.src
= window
.location
;
569 function onIframeLoaded() {
570 var iframe
= window
.document
.querySelector('iframe');
572 // Propagate test success out of the iframe.
573 iframe
.contentWindow
.setAllEventsOccuredHandler(
574 window
.parent
.reportTestSuccess
);
580 // This function is used for setting up a test that:
581 // 1. Creates a data channel on |gFirstConnection| and sends data to
582 // |gSecondConnection|.
583 // 2. When data is received on |gSecondConnection| a message
584 // is sent to |gFirstConnection|.
585 // 3. When data is received on |gFirstConnection|, the data
586 // channel is closed. The test passes when the state transition completes.
587 function setupDataChannel(params
) {
588 var sendDataString
= "send some text on a data channel."
589 firstDataChannel
= gFirstConnection
.createDataChannel(
590 "sendDataChannel", params
);
591 assertEquals('connecting', firstDataChannel
.readyState
);
593 // When |firstDataChannel| transition to open state, send a text string.
594 firstDataChannel
.onopen = function() {
595 assertEquals('open', firstDataChannel
.readyState
);
596 firstDataChannel
.send(sendDataString
);
599 // When |firstDataChannel| receive a message, close the channel and
600 // initiate a new offer/answer exchange to complete the closure.
601 firstDataChannel
.onmessage = function(event
) {
602 assertEquals(event
.data
, sendDataString
);
603 firstDataChannel
.close();
607 // When |firstDataChannel| transition to closed state, the test pass.
609 firstDataChannel
.onclose = function() {
610 assertEquals('closed', firstDataChannel
.readyState
);
614 // Event handler for when |gSecondConnection| receive a new dataChannel.
615 gSecondConnection
.ondatachannel = function (event
) {
616 // Make secondDataChannel global to make sure it's not gc'd.
617 secondDataChannel
= event
.channel
;
619 // When |secondDataChannel| receive a message, send a message back.
620 secondDataChannel
.onmessage = function(event
) {
621 assertEquals(event
.data
, sendDataString
);
622 console
.log("gSecondConnection received data");
623 assertEquals('open', secondDataChannel
.readyState
);
624 secondDataChannel
.send(sendDataString
);
629 // SCTP data channel setup is slightly different then RTP based
630 // channels. Due to a bug in libjingle, we can't send data immediately
631 // after channel becomes open. So for that reason in SCTP,
632 // we are sending data from second channel, when ondatachannel event is
633 // received. So data flow happens 2 -> 1 -> 2.
634 function setupSctpDataChannel(params
) {
635 var sendDataString
= "send some text on a data channel."
636 firstDataChannel
= gFirstConnection
.createDataChannel(
637 "sendDataChannel", params
);
638 assertEquals('connecting', firstDataChannel
.readyState
);
640 // When |firstDataChannel| transition to open state, send a text string.
641 firstDataChannel
.onopen = function() {
642 assertEquals('open', firstDataChannel
.readyState
);
645 // When |firstDataChannel| receive a message, send message back.
646 // initiate a new offer/answer exchange to complete the closure.
647 firstDataChannel
.onmessage = function(event
) {
648 assertEquals('open', firstDataChannel
.readyState
);
649 assertEquals(event
.data
, sendDataString
);
650 firstDataChannel
.send(sendDataString
);
653 // Event handler for when |gSecondConnection| receive a new dataChannel.
654 gSecondConnection
.ondatachannel = function (event
) {
655 // Make secondDataChannel global to make sure it's not gc'd.
656 secondDataChannel
= event
.channel
;
657 secondDataChannel
.onopen = function() {
658 secondDataChannel
.send(sendDataString
);
661 // When |secondDataChannel| receive a message, close the channel and
662 // initiate a new offer/answer exchange to complete the closure.
663 secondDataChannel
.onmessage = function(event
) {
664 assertEquals(event
.data
, sendDataString
);
665 assertEquals('open', secondDataChannel
.readyState
);
666 secondDataChannel
.close();
670 // When |secondDataChannel| transition to closed state, the test pass.
672 secondDataChannel
.onclose = function() {
673 assertEquals('closed', secondDataChannel
.readyState
);
679 // Test call with a stream that has been created by getUserMedia, clone
680 // the stream to a cloned stream, send them via the same peer connection.
681 function addTwoMediaStreamsToOneConnection() {
682 createConnections(null);
683 navigator
.webkitGetUserMedia({audio
: true, video
: true},
684 cloneStreamAndAddTwoStreamsToOneConnection
, printGetUserMediaError
);
687 function onToneChange(tone
) {
688 gSentTones
+= tone
.tone
;
691 function createConnections(constraints
) {
692 gFirstConnection
= createConnection(constraints
, 'remote-view-1');
693 assertEquals('stable', gFirstConnection
.signalingState
);
695 gSecondConnection
= createConnection(constraints
, 'remote-view-2');
696 assertEquals('stable', gSecondConnection
.signalingState
);
699 function createConnection(constraints
, remoteView
) {
700 var pc
= new webkitRTCPeerConnection(null, constraints
);
701 pc
.onaddstream = function(event
) {
702 onRemoteStream(event
, remoteView
);
707 function displayAndRemember(localStream
) {
708 var localStreamUrl
= URL
.createObjectURL(localStream
);
709 $('local-view').src
= localStreamUrl
;
711 gLocalStream
= localStream
;
714 // Called if getUserMedia fails.
715 function printGetUserMediaError(error
) {
716 var message
= 'getUserMedia request unexpectedly failed:';
717 if (error
.constraintName
)
718 message
+= ' could not satisfy constraint ' + error
.constraintName
;
720 message
+= ' devices not working/user denied access.';
724 // Called if getUserMedia succeeds and we want to send from both connections.
725 function addStreamToBothConnectionsAndNegotiate(localStream
) {
726 displayAndRemember(localStream
);
727 gFirstConnection
.addStream(localStream
);
728 gSecondConnection
.addStream(localStream
);
732 // Called if getUserMedia succeeds when we want to send from one connection.
733 function addStreamToTheFirstConnectionAndNegotiate(localStream
) {
734 displayAndRemember(localStream
);
735 gFirstConnection
.addStream(localStream
);
739 function verifyHasOneAudioAndVideoTrack(stream
) {
740 assertEquals(1, stream
.getAudioTracks().length
);
741 assertEquals(1, stream
.getVideoTracks().length
);
744 // Called if getUserMedia succeeds, then clone the stream, send two streams
745 // from one peer connection.
746 function cloneStreamAndAddTwoStreamsToOneConnection(localStream
) {
747 displayAndRemember(localStream
);
749 var clonedStream
= null;
750 if (typeof localStream
.clone
=== "function") {
751 clonedStream
= localStream
.clone();
753 clonedStream
= new webkitMediaStream(localStream
);
756 gFirstConnection
.addStream(localStream
);
757 gFirstConnection
.addStream(clonedStream
);
759 // Verify the local streams are correct.
760 assertEquals(2, gFirstConnection
.getLocalStreams().length
);
761 verifyHasOneAudioAndVideoTrack(gFirstConnection
.getLocalStreams()[0]);
762 verifyHasOneAudioAndVideoTrack(gFirstConnection
.getLocalStreams()[1]);
764 // The remote side should receive two streams. After that, verify the
765 // remote side has the correct number of streams and tracks.
768 gSecondConnection
.onaddstream = function(event
) {
771 setAllEventsOccuredHandler(function() {
772 // Negotiation complete, verify remote streams on the receiving side.
773 assertEquals(2, gSecondConnection
.getRemoteStreams().length
);
774 verifyHasOneAudioAndVideoTrack(gSecondConnection
.getRemoteStreams()[0]);
775 verifyHasOneAudioAndVideoTrack(gSecondConnection
.getRemoteStreams()[1]);
783 // A new MediaStream is created with video track from |localStream| and is
784 // added to both peer connections.
785 function createNewVideoStreamAndAddToBothConnections(localStream
) {
786 displayAndRemember(localStream
);
787 var newStream
= new webkitMediaStream();
788 newStream
.addTrack(localStream
.getVideoTracks()[0]);
789 gFirstConnection
.addStream(newStream
);
790 gSecondConnection
.addStream(newStream
);
794 function negotiate() {
795 negotiateBetween(gFirstConnection
, gSecondConnection
);
798 function negotiateBetween(caller
, callee
) {
799 console
.log("Negotiating call...");
800 // Not stable = negotiation is ongoing. The behavior of re-negotiating while
801 // a negotiation is ongoing is more or less undefined, so avoid this.
802 if (caller
.signalingState
!= 'stable' || callee
.signalingState
!= 'stable')
803 throw 'You can only negotiate when the connection is stable!';
805 connectOnIceCandidate(caller
, callee
);
809 onOfferCreated(offer
, caller
, callee
);
813 function onOfferCreated(offer
, caller
, callee
) {
814 offer
.sdp
= transformSdp(offer
.sdp
);
815 console
.log('Offer:\n' + offer
.sdp
);
816 caller
.setLocalDescription(offer
, function() {
817 assertEquals('have-local-offer', caller
.signalingState
);
818 receiveOffer(offer
.sdp
, caller
, callee
);
819 }, onLocalDescriptionError
);
822 function receiveOffer(offerSdp
, caller
, callee
) {
823 console
.log("Receiving offer...");
824 offerSdp
= transformRemoteSdp(offerSdp
);
826 var parsedOffer
= new RTCSessionDescription({ type
: 'offer',
828 callee
.setRemoteDescription(parsedOffer
,
830 assertEquals('have-remote-offer',
831 callee
.signalingState
);
834 onAnswerCreated(answer
, caller
, callee
);
837 onRemoteDescriptionError
);
840 function removeMsid(offerSdp
) {
841 offerSdp
= offerSdp
.replace(/a=msid-semantic.*\r\n/g, '');
842 offerSdp
= offerSdp
.replace('a=mid:audio\r\n', '');
843 offerSdp
= offerSdp
.replace('a=mid:video\r\n', '');
844 offerSdp
= offerSdp
.replace(/a=ssrc.*\r\n/g, '');
848 function removeVideoCodec(offerSdp
) {
849 offerSdp
= offerSdp
.replace('a=rtpmap:100 VP8/90000\r\n',
850 'a=rtpmap:100 XVP8/90000\r\n');
854 function removeCrypto(offerSdp
) {
855 offerSdp
= offerSdp
.replace(/a=crypto.*\r\n/g, 'a=Xcrypto\r\n');
856 offerSdp
= offerSdp
.replace(/a=fingerprint.*\r\n/g, '');
860 function addBandwithControl(offerSdp
) {
861 offerSdp
= offerSdp
.replace('a=mid:audio\r\n', 'a=mid:audio\r\n'+
863 offerSdp
= offerSdp
.replace('a=mid:video\r\n', 'a=mid:video\r\n'+
868 function removeBundle(sdp
) {
869 return sdp
.replace(/a=group:BUNDLE .*\r\n/g, '');
872 function useGice(sdp
) {
873 sdp
= sdp
.replace(/t=.*\r\n/g, function(subString
) {
874 return subString
+ 'a=ice-options:google-ice\r\n';
879 function useExternalSdes(sdp
) {
880 // Remove current crypto specification.
881 sdp
= sdp
.replace(/a=crypto.*\r\n/g, '');
882 sdp
= sdp
.replace(/a=fingerprint.*\r\n/g, '');
883 // Add external crypto. This is not compatible with |removeMsid|.
884 sdp
= sdp
.replace(/a=mid:(\w+)\r\n/g, function(subString
, group
) {
885 return subString
+ EXTERNAL_SDES_LINES
[group
] + '\r\n';
890 function onAnswerCreated(answer
, caller
, callee
) {
891 answer
.sdp
= transformSdp(answer
.sdp
);
892 console
.log('Answer:\n' + answer
.sdp
);
893 callee
.setLocalDescription(answer
,
895 assertEquals('stable', callee
.signalingState
);
897 onLocalDescriptionError
);
898 receiveAnswer(answer
.sdp
, caller
);
901 function receiveAnswer(answerSdp
, caller
) {
902 console
.log("Receiving answer...");
903 answerSdp
= transformRemoteSdp(answerSdp
);
904 var parsedAnswer
= new RTCSessionDescription({ type
: 'answer',
906 caller
.setRemoteDescription(parsedAnswer
,
908 assertEquals('stable', caller
.signalingState
);
910 onRemoteDescriptionError
);
913 function connectOnIceCandidate(caller
, callee
) {
914 caller
.onicecandidate = function(event
) { onIceCandidate(event
, callee
); }
915 callee
.onicecandidate = function(event
) { onIceCandidate(event
, caller
); }
918 function onIceCandidate(event
, target
) {
919 if (event
.candidate
) {
920 var candidate
= new RTCIceCandidate(event
.candidate
);
921 target
.addIceCandidate(candidate
);
925 function onRemoteStream(e
, target
) {
926 console
.log("Receiving remote stream...");
927 if (gTestWithoutMsid
&& e
.stream
.id
!= "default") {
928 failTest('a default remote stream was expected but instead ' +
929 e
.stream
.id
+ ' was received.');
931 gRemoteStreams
[target
] = e
.stream
;
932 var remoteStreamUrl
= URL
.createObjectURL(e
.stream
);
933 var remoteVideo
= $(target
);
934 remoteVideo
.src
= remoteStreamUrl
;
942 <td><video width=
"320" height=
"240" id=
"local-view" style=
"display:none"
943 autoplay muted
></video></td>
944 <td><video width=
"320" height=
"240" id=
"remote-view-1"
945 style=
"display:none" autoplay
></video></td>
946 <td><video width=
"320" height=
"240" id=
"remote-view-2"
947 style=
"display:none" autoplay
></video></td>
948 <td><video width=
"320" height=
"240" id=
"remote-view-3"
949 style=
"display:none" autoplay
></video></td>
950 <td><video width=
"320" height=
"240" id=
"remote-view-4"
951 style=
"display:none" autoplay
></video></td>
952 <!-- Canvases are named after their corresponding video elements. -->
953 <td><canvas width=
"320" height=
"240" id=
"remote-view-1-canvas"
954 style=
"display:none"></canvas></td>
955 <td><canvas width=
"320" height=
"240" id=
"remote-view-2-canvas"
956 style=
"display:none"></canvas></td>
957 <td><canvas width=
"320" height=
"240" id=
"remote-view-3-canvas"
958 style=
"display:none"></canvas></td>
959 <td><canvas width=
"320" height=
"240" id=
"remote-view-4-canvas"
960 style=
"display:none"></canvas></td>