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 var gFirstConnection = null;
11 var gSecondConnection = null;
12 var gTestWithoutMsid = false;
13 var gLocalStream = null;
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 transformCandidate = function(candidate) { return candidate; };
22 var onLocalDescriptionError = function(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
27 var maybeForceIsac16K = function(sdp) { return sdp; };
28 function forceIsac16KInSdp() {
29 maybeForceIsac16K = function(sdp) {
30 // Remove all other codecs (not the video codecs though). Also leave
32 sdp = sdp.replace(/m=audio (\d+) RTP\/SAVPF.*\r\n/g,
33 'm=audio $1 RTP/SAVPF 103 126\r\n');
34 sdp = sdp.replace('a=fmtp:111 minptime=10', 'a=fmtp:103 minptime=10');
36 /a=rtpmap:(?!(103|126))\d{1,3} (?!X?VP8|red|ulpfec).*\r\n/g,
42 // When using external SDES, the crypto key is chosen by javascript.
43 var EXTERNAL_SDES_LINES = {
44 'audio': 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' +
45 'inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR',
46 'video': 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' +
47 'inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj',
48 'data': 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' +
49 'inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj'
52 // When using GICE, the ICE credentials can be chosen by javascript.
53 var EXTERNAL_GICE_UFRAG = '1234567890123456';
54 var EXTERNAL_GICE_PWD = '123456789012345678901234';
56 setAllEventsOccuredHandler(function() {
57 // The C++ tests look for this 'OK' in the title.
58 document.title = 'OK';
61 // Test that we can setup call with an audio and video track.
62 function call(constraints) {
63 createConnections(null);
64 navigator.webkitGetUserMedia(constraints,
65 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
66 waitForVideo('remote-view-1');
67 waitForVideo('remote-view-2');
70 // First calls without streams on any connections, and then adds a stream
71 // to peer connection 1 which gets sent to peer connection 2. We must wait
72 // for the first negotiation to complete before starting the second one, which
73 // is why we wait until the connection is stable before re-negotiating.
74 function callEmptyThenAddOneStreamAndRenegotiate(constraints) {
75 createConnections(null);
77 waitForConnectionToStabilize(gFirstConnection, function() {
78 navigator.webkitGetUserMedia(constraints,
79 addStreamToTheFirstConnectionAndNegotiate, printGetUserMediaError);
80 // Only the first connection is sending here.
81 waitForVideo('remote-view-2');
85 // First makes a call between pc1 and pc2, and then makes a call between pc3
86 // and pc4 where the remote streams from pc1 and pc2 will be used as the local
87 // streams of pc3 and pc4.
88 function callAndForwardRemoteStream(constraints) {
89 createConnections(null);
90 navigator.webkitGetUserMedia(constraints,
91 addStreamToBothConnectionsAndNegotiate,
92 printGetUserMediaError);
93 var gotRemoteStream1 = false;
94 var gotRemoteStream2 = false;
96 var onRemoteStream1 = function() {
97 gotRemoteStream1 = true;
98 maybeCallEstablished();
101 var onRemoteStream2 = function() {
102 gotRemoteStream2 = true;
103 maybeCallEstablished();
106 var maybeCallEstablished = function() {
107 if (gotRemoteStream1 && gotRemoteStream2) {
112 var onCallEstablished = function() {
113 thirdConnection = createConnection(null, 'remote-view-3');
114 thirdConnection.addStream(gRemoteStreams['remote-view-1']);
116 fourthConnection = createConnection(null, 'remote-view-4');
117 fourthConnection.addStream(gRemoteStreams['remote-view-2']);
119 negotiateBetween(thirdConnection, fourthConnection);
121 waitForVideo('remote-view-3');
122 waitForVideo('remote-view-4');
125 // Do the forwarding after we have received video.
126 detectVideoPlaying('remote-view-1', onRemoteStream1);
127 detectVideoPlaying('remote-view-2', onRemoteStream2);
130 // Test that we can setup call with an audio and video track and
131 // simulate that the remote peer don't support MSID.
132 function callWithoutMsidAndBundle() {
133 createConnections(null);
134 transformSdp = removeBundle;
135 transformRemoteSdp = removeMsid;
136 gTestWithoutMsid = true;
137 navigator.webkitGetUserMedia({audio: true, video: true},
138 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
139 waitForVideo('remote-view-1');
140 waitForVideo('remote-view-2');
143 // Test that we can't setup a call with an unsupported video codec
144 function negotiateUnsupportedVideoCodec() {
145 createConnections(null);
146 transformSdp = removeVideoCodec;
147 onLocalDescriptionError = function(error) {
148 var expectedMsg = 'Failed to set local offer sdp:' +
149 ' Session error code: ERROR_CONTENT. Session error description:' +
150 ' Failed to set video receive codecs..';
151 expectEquals(expectedMsg, error);
153 // Got the right message, test succeeded.
154 document.title = 'OK';
156 navigator.webkitGetUserMedia({audio: true, video: true},
157 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
160 // Test that we can't setup a call if one peer does not support encryption
161 function negotiateNonCryptoCall() {
162 createConnections(null);
163 transformSdp = removeCrypto;
164 onLocalDescriptionError = function(error) {
165 var expectedMsg = 'Failed to set local offer sdp:' +
166 ' Called with a SDP without crypto enabled.';
167 expectEquals(expectedMsg, error);
169 // Got the right message, test succeeded.
170 document.title = 'OK';
172 navigator.webkitGetUserMedia({audio: true, video: true},
173 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
176 // Test that we can negotiate a call with an SDP offer that includes a
177 // b=AS:XX line to control audio and video bandwidth
178 function negotiateOfferWithBLine() {
179 createConnections(null);
180 transformSdp = addBandwithControl;
181 navigator.webkitGetUserMedia({audio: true, video: true},
182 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
183 waitForVideo('remote-view-1');
184 waitForVideo('remote-view-2');
187 // Test that we can setup call with legacy settings.
188 function callWithLegacySdp() {
189 transformSdp = function(sdp) {
190 return removeBundle(useGice(useExternalSdes(sdp)));
192 transformCandidate = addGiceCredsToCandidate;
194 'mandatory': {'RtpDataChannels': true, 'DtlsSrtpKeyAgreement': false}
196 setupDataChannel({reliable: false});
197 navigator.webkitGetUserMedia({audio: true, video: true},
198 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
199 waitForVideo('remote-view-1');
200 waitForVideo('remote-view-2');
203 // Test only a data channel.
204 function callWithDataOnly() {
205 createConnections({optional:[{RtpDataChannels: true}]});
206 setupDataChannel({reliable: false});
210 function callWithSctpDataOnly() {
211 createConnections({optional: [{DtlsSrtpKeyAgreement: true}]});
212 setupSctpDataChannel({reliable: true});
216 // Test call with audio, video and a data channel.
217 function callWithDataAndMedia() {
218 createConnections({optional:[{RtpDataChannels: true}]});
219 setupDataChannel({reliable: false});
220 navigator.webkitGetUserMedia({audio: true, video: true},
221 addStreamToBothConnectionsAndNegotiate,
222 printGetUserMediaError);
223 waitForVideo('remote-view-1');
224 waitForVideo('remote-view-2');
227 function callWithSctpDataAndMedia() {
228 createConnections({optional: [{DtlsSrtpKeyAgreement: true}]});
229 setupSctpDataChannel({reliable: true});
230 navigator.webkitGetUserMedia({audio: true, video: true},
231 addStreamToBothConnectionsAndNegotiate,
232 printGetUserMediaError);
233 waitForVideo('remote-view-1');
234 waitForVideo('remote-view-2');
238 // Test call with a data channel and later add audio and video.
239 function callWithDataAndLaterAddMedia() {
240 createConnections({optional:[{RtpDataChannels: true}]});
241 setupDataChannel({reliable: false});
244 // Set an event handler for when the data channel has been closed.
245 setAllEventsOccuredHandler(function() {
246 // When the video is flowing the test is done.
247 setAllEventsOccuredHandler(function() {
248 document.title = 'OK';
250 navigator.webkitGetUserMedia({audio: true, video: true},
251 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
252 waitForVideo('remote-view-1');
253 waitForVideo('remote-view-2');
257 // Test that we can setup call and send DTMF.
258 function callAndSendDtmf(tones) {
259 createConnections(null);
260 navigator.webkitGetUserMedia({audio: true, video: true},
261 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
262 var onCallEstablished = function() {
264 var localAudioTrack = gLocalStream.getAudioTracks()[0];
265 var dtmfSender = gFirstConnection.createDTMFSender(localAudioTrack);
266 dtmfSender.ontonechange = onToneChange;
267 dtmfSender.insertDTMF(tones);
268 // Wait for the DTMF tones callback.
269 document.title = 'Waiting for dtmf...';
271 var waitDtmf = setInterval(function() {
272 if (gSentTones == tones) {
273 clearInterval(waitDtmf);
279 // Do the DTMF test after we have received video.
280 detectVideoPlaying('remote-view-2', onCallEstablished);
283 function enableRemoteVideo(peerConnection, enabled) {
284 remoteStream = peerConnection.getRemoteStreams()[0];
285 remoteVideoTrack = remoteStream.getVideoTracks()[0];
286 remoteVideoTrack.enabled = enabled;
289 function enableRemoteAudio(peerConnection, enabled) {
290 remoteStream = peerConnection.getRemoteStreams()[0];
291 remoteAudioTrack = remoteStream.getAudioTracks()[0];
292 remoteAudioTrack.enabled = enabled;
295 function callAndEnsureAudioIsPlaying() {
296 createConnections(null);
297 navigator.webkitGetUserMedia({audio: true, video: true},
298 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
300 // Wait until we have gathered samples and can conclude if audio is playing.
302 var onCallEstablished = function() {
303 gatherAudioLevelSamples(gSecondConnection, 300, 100,
305 verifyAudioIsPlaying(samples);
309 // (Also, ensure video muting doesn't affect audio).
310 enableRemoteVideo(gSecondConnection, false);
313 detectVideoPlaying('remote-view-2', onCallEstablished);
316 function callAndEnsureAudioMutingWorks() {
317 callAndEnsureAudioIsPlaying();
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, 200, 100, function(samples) {
325 verifyIsSilent(samples);
326 document.title = 'OK';
332 function callAndEnsureVideoMutingWorks() {
333 createConnections(null);
334 navigator.webkitGetUserMedia({audio: true, video: true},
335 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
338 detectVideoPlaying('remote-view-2', function() {
339 // Disable the receiver's remote media stream. Video should stop.
340 // (Also, ensure muting audio doesn't affect video).
341 enableRemoteVideo(gSecondConnection, false);
342 enableRemoteAudio(gSecondConnection, false);
344 detectVideoStopped('remote-view-2', function() {
345 // Video has stopped: unmute and succeed if it starts playing again.
346 enableRemoteVideo(gSecondConnection, true);
347 detectVideoPlaying('remote-view-2', eventOccured);
352 // Test call with a new Video MediaStream that has been created based on a
353 // stream generated by getUserMedia.
354 function callWithNewVideoMediaStream() {
355 createConnections(null);
356 navigator.webkitGetUserMedia({audio: true, video: true},
357 createNewVideoStreamAndAddToBothConnections, printGetUserMediaError);
358 waitForVideo('remote-view-1');
359 waitForVideo('remote-view-2');
362 // Test call with a new Video MediaStream that has been created based on a
363 // stream generated by getUserMedia. When Video is flowing, an audio track
364 // is added to the sent stream and the video track is removed. This
365 // is to test that adding and removing of remote tracks on an existing
366 // mediastream works.
367 function callWithNewVideoMediaStreamLaterSwitchToAudio() {
368 createConnections(null);
369 navigator.webkitGetUserMedia({audio: true, video: true},
370 createNewVideoStreamAndAddToBothConnections, printGetUserMediaError);
372 waitForVideo('remote-view-1');
373 waitForVideo('remote-view-2');
375 // Set an event handler for when video is playing.
376 setAllEventsOccuredHandler(function() {
377 // Add an audio track to the local stream and remove the video track and
378 // then renegotiate. But first - setup the expectations.
379 local_stream = gFirstConnection.getLocalStreams()[0];
381 remote_stream_1 = gFirstConnection.getRemoteStreams()[0];
382 // Add an expected event that onaddtrack will be called on the remote
383 // mediastream received on gFirstConnection when the audio track is
386 remote_stream_1.onaddtrack = function(){
387 expectEquals(remote_stream_1.getAudioTracks()[0].id,
388 local_stream.getAudioTracks()[0].id);
392 // Add an expectation that the received video track is removed from
395 remote_stream_1.onremovetrack = function() {
399 // Add an expected event that onaddtrack will be called on the remote
400 // mediastream received on gSecondConnection when the audio track is
402 remote_stream_2 = gSecondConnection.getRemoteStreams()[0];
404 remote_stream_2.onaddtrack = function() {
405 expectEquals(remote_stream_2.getAudioTracks()[0].id,
406 local_stream.getAudioTracks()[0].id);
410 // Add an expectation that the received video track is removed from
411 // gSecondConnection.
413 remote_stream_2.onremovetrack = function() {
416 // When all the above events have occurred- the test pass.
417 setAllEventsOccuredHandler(function() { document.title = 'OK'; });
419 local_stream.addTrack(gLocalStream.getAudioTracks()[0]);
420 local_stream.removeTrack(local_stream.getVideoTracks()[0]);
425 // This function is used for setting up a test that:
426 // 1. Creates a data channel on |gFirstConnection| and sends data to
427 // |gSecondConnection|.
428 // 2. When data is received on |gSecondConnection| a message
429 // is sent to |gFirstConnection|.
430 // 3. When data is received on |gFirstConnection|, the data
431 // channel is closed. The test passes when the state transition completes.
432 function setupDataChannel(params) {
433 var sendDataString = "send some text on a data channel."
434 firstDataChannel = gFirstConnection.createDataChannel(
435 "sendDataChannel", params);
436 expectEquals('connecting', firstDataChannel.readyState);
438 // When |firstDataChannel| transition to open state, send a text string.
439 firstDataChannel.onopen = function() {
440 expectEquals('open', firstDataChannel.readyState);
441 firstDataChannel.send(sendDataString);
444 // When |firstDataChannel| receive a message, close the channel and
445 // initiate a new offer/answer exchange to complete the closure.
446 firstDataChannel.onmessage = function(event) {
447 expectEquals(event.data, sendDataString);
448 firstDataChannel.close();
452 // When |firstDataChannel| transition to closed state, the test pass.
454 firstDataChannel.onclose = function() {
455 expectEquals('closed', firstDataChannel.readyState);
459 // Event handler for when |gSecondConnection| receive a new dataChannel.
460 gSecondConnection.ondatachannel = function (event) {
461 var secondDataChannel = event.channel;
463 // When |secondDataChannel| receive a message, send a message back.
464 secondDataChannel.onmessage = function(event) {
465 expectEquals(event.data, sendDataString);
466 expectEquals('open', secondDataChannel.readyState);
467 secondDataChannel.send(sendDataString);
472 // SCTP data channel setup is slightly different then RTP based
473 // channels. Due to a bug in libjingle, we can't send data immediately
474 // after channel becomes open. So for that reason in SCTP,
475 // we are sending data from second channel, when ondatachannel event is
476 // received. So data flow happens 2 -> 1 -> 2.
477 function setupSctpDataChannel(params) {
478 var sendDataString = "send some text on a data channel."
479 firstDataChannel = gFirstConnection.createDataChannel(
480 "sendDataChannel", params);
481 expectEquals('connecting', firstDataChannel.readyState);
483 // When |firstDataChannel| transition to open state, send a text string.
484 firstDataChannel.onopen = function() {
485 expectEquals('open', firstDataChannel.readyState);
488 // When |firstDataChannel| receive a message, send message back.
489 // initiate a new offer/answer exchange to complete the closure.
490 firstDataChannel.onmessage = function(event) {
491 expectEquals('open', firstDataChannel.readyState);
492 expectEquals(event.data, sendDataString);
493 firstDataChannel.send(sendDataString);
497 // Event handler for when |gSecondConnection| receive a new dataChannel.
498 gSecondConnection.ondatachannel = function (event) {
499 var secondDataChannel = event.channel;
500 secondDataChannel.onopen = function() {
501 secondDataChannel.send(sendDataString);
504 // When |secondDataChannel| receive a message, close the channel and
505 // initiate a new offer/answer exchange to complete the closure.
506 secondDataChannel.onmessage = function(event) {
507 expectEquals(event.data, sendDataString);
508 expectEquals('open', secondDataChannel.readyState);
509 secondDataChannel.close();
513 // When |secondDataChannel| transition to closed state, the test pass.
515 secondDataChannel.onclose = function() {
516 expectEquals('closed', secondDataChannel.readyState);
522 // Test call with a stream that has been created by getUserMedia, clone
523 // the stream to a cloned stream, send them via the same peer connection.
524 function addTwoMediaStreamsToOneConnection() {
525 createConnections(null);
526 navigator.webkitGetUserMedia({audio: true, video: true},
527 CloneStreamAndAddTwoStreamstoOneConnection, printGetUserMediaError);
530 function onToneChange(tone) {
531 gSentTones += tone.tone;
532 document.title = gSentTones;
535 function createConnections(constraints) {
536 gFirstConnection = createConnection(constraints, 'remote-view-1');
537 expectEquals('stable', gFirstConnection.signalingState);
539 gSecondConnection = createConnection(constraints, 'remote-view-2');
540 expectEquals('stable', gSecondConnection.signalingState);
543 function createConnection(constraints, remoteView) {
544 var pc = new webkitRTCPeerConnection(null, constraints);
545 pc.onaddstream = function(event) {
546 onRemoteStream(event, remoteView);
551 function displayAndRemember(localStream) {
552 var localStreamUrl = URL.createObjectURL(localStream);
553 $('local-view').src = localStreamUrl;
555 gLocalStream = localStream;
558 // Called if getUserMedia fails.
559 function printGetUserMediaError(error) {
560 document.title = 'getUserMedia request failed:';
561 if (error.constraintName)
562 document.title += ' could not satisfy constraint ' + error.constraintName;
564 document.title += ' devices not working/user denied access.';
565 console.log(document.title);
568 // Called if getUserMedia succeeds and we want to send from both connections.
569 function addStreamToBothConnectionsAndNegotiate(localStream) {
570 displayAndRemember(localStream);
571 gFirstConnection.addStream(localStream);
572 gSecondConnection.addStream(localStream);
576 // Called if getUserMedia succeeds when we want to send from one connection.
577 function addStreamToTheFirstConnectionAndNegotiate(localStream) {
578 displayAndRemember(localStream);
579 gFirstConnection.addStream(localStream);
583 function verifyHasOneAudioAndVideoTrack(stream) {
584 expectEquals(1, stream.getAudioTracks().length);
585 expectEquals(1, stream.getVideoTracks().length);
588 // Called if getUserMedia succeeds, then clone the stream, send two streams
589 // from one peer connection.
590 function CloneStreamAndAddTwoStreamstoOneConnection(localStream) {
591 displayAndRemember(localStream);
593 var clonedStream = null;
594 if (typeof localStream.clone === "function") {
595 clonedStream = localStream.clone();
597 clonedStream = new webkitMediaStream(localStream);
600 gFirstConnection.addStream(localStream);
601 gFirstConnection.addStream(clonedStream);
603 // Verify the local streams are correct.
604 expectEquals(2, gFirstConnection.getLocalStreams().length);
605 verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[0]);
606 verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[1]);
608 // The remote side should receive two streams. After that, verify the
609 // remote side has the correct number of streams and tracks.
612 gSecondConnection.onaddstream = function(event) {
615 setAllEventsOccuredHandler(function() {
616 // Negotiation complete, verify remote streams on the receiving side.
617 expectEquals(2, gSecondConnection.getRemoteStreams().length);
618 verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[0]);
619 verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[1]);
621 document.title = "OK";
627 // Called if getUserMedia succeeds when we want to send a modified
628 // MediaStream. A new MediaStream is created and the video track from
629 // |localStream| is added.
630 function createNewVideoStreamAndAddToBothConnections(localStream) {
631 displayAndRemember(localStream);
632 var new_stream = new webkitMediaStream();
633 new_stream.addTrack(localStream.getVideoTracks()[0]);
634 gFirstConnection.addStream(new_stream);
635 gSecondConnection.addStream(new_stream);
639 function negotiate() {
640 negotiateBetween(gFirstConnection, gSecondConnection);
643 function negotiateBetween(caller, callee) {
644 console.log("Negotiating call...");
645 // Not stable = negotiation is ongoing. The behavior of re-negotiating while
646 // a negotiation is ongoing is more or less undefined, so avoid this.
647 if (caller.signalingState != 'stable')
648 throw 'You can only negotiate when the connection is stable!';
650 connectOnIceCandidate(caller, callee);
654 onOfferCreated(offer, caller, callee);
658 function onOfferCreated(offer, caller, callee) {
659 offer.sdp = maybeForceIsac16K(transformSdp(offer.sdp));
660 caller.setLocalDescription(offer, function() {
661 expectEquals('have-local-offer', caller.signalingState);
662 receiveOffer(offer.sdp, caller, callee);
663 }, onLocalDescriptionError);
666 function receiveOffer(offerSdp, caller, callee) {
667 console.log("Receiving offer...");
668 offerSdp = transformRemoteSdp(offerSdp);
670 var parsedOffer = new RTCSessionDescription({ type: 'offer',
672 callee.setRemoteDescription(parsedOffer);
673 callee.createAnswer(function (answer) {
674 onAnswerCreated(answer, caller, callee);
676 expectEquals('have-remote-offer', callee.signalingState);
679 function removeMsid(offerSdp) {
680 offerSdp = offerSdp.replace(/a=msid-semantic.*\r\n/g, '');
681 offerSdp = offerSdp.replace('a=mid:audio\r\n', '');
682 offerSdp = offerSdp.replace('a=mid:video\r\n', '');
683 offerSdp = offerSdp.replace(/a=ssrc.*\r\n/g, '');
687 function removeVideoCodec(offerSdp) {
688 offerSdp = offerSdp.replace('a=rtpmap:100 VP8/90000\r\n',
689 'a=rtpmap:100 XVP8/90000\r\n');
693 function removeCrypto(offerSdp) {
694 offerSdp = offerSdp.replace(/a=crypto.*\r\n/g, 'a=Xcrypto\r\n');
695 offerSdp = offerSdp.replace(/a=fingerprint.*\r\n/g, '');
699 function addBandwithControl(offerSdp) {
700 offerSdp = offerSdp.replace('a=mid:audio\r\n', 'a=mid:audio\r\n'+
702 offerSdp = offerSdp.replace('a=mid:video\r\n', 'a=mid:video\r\n'+
707 function removeBundle(sdp) {
708 return sdp.replace(/a=group:BUNDLE .*\r\n/g, '');
711 function useGice(sdp) {
712 sdp = sdp.replace(/t=.*\r\n/g, function(subString) {
713 return subString + 'a=ice-options:google-ice\r\n';
715 sdp = sdp.replace(/a=ice-ufrag:.*\r\n/g,
716 'a=ice-ufrag:' + EXTERNAL_GICE_UFRAG + '\r\n');
717 sdp = sdp.replace(/a=ice-pwd:.*\r\n/g,
718 'a=ice-pwd:' + EXTERNAL_GICE_PWD + '\r\n');
722 function useExternalSdes(sdp) {
723 // Remove current crypto specification.
724 sdp = sdp.replace(/a=crypto.*\r\n/g, '');
725 sdp = sdp.replace(/a=fingerprint.*\r\n/g, '');
726 // Add external crypto. This is not compatible with |removeMsid|.
727 sdp = sdp.replace(/a=mid:(\w+)\r\n/g, function(subString, group) {
728 return subString + EXTERNAL_SDES_LINES[group] + '\r\n';
733 function onAnswerCreated(answer, caller, callee) {
734 answer.sdp = maybeForceIsac16K(transformSdp(answer.sdp));
735 callee.setLocalDescription(answer);
736 expectEquals('stable', callee.signalingState);
737 receiveAnswer(answer.sdp, caller);
740 function receiveAnswer(answerSdp, caller) {
741 console.log("Receiving answer...");
742 answerSdp = transformRemoteSdp(answerSdp);
743 var parsedAnswer = new RTCSessionDescription({ type: 'answer',
745 caller.setRemoteDescription(parsedAnswer);
746 expectEquals('stable', caller.signalingState);
749 function connectOnIceCandidate(caller, callee) {
750 caller.onicecandidate = function(event) { onIceCandidate(event, callee); }
751 callee.onicecandidate = function(event) { onIceCandidate(event, caller); }
754 function addGiceCredsToCandidate(candidate) {
755 return candidate.trimRight() +
756 ' username ' + EXTERNAL_GICE_UFRAG + ' password ' + EXTERNAL_GICE_PWD;
759 function onIceCandidate(event, target) {
760 if (event.candidate) {
761 var candidate = new RTCIceCandidate(event.candidate);
762 candidate.candidate = transformCandidate(candidate.candidate);
763 target.addIceCandidate(candidate);
767 function onRemoteStream(e, target) {
768 console.log("Receiving remote stream...");
769 if (gTestWithoutMsid && e.stream.id != "default") {
770 document.title = 'a default remote stream was expected but instead ' +
771 e.stream.id + ' was received.';
774 gRemoteStreams[target] = e.stream;
775 var remoteStreamUrl = URL.createObjectURL(e.stream);
776 var remoteVideo = $(target);
777 remoteVideo.src = remoteStreamUrl;
785 <td>Local Preview</td>
786 <td>Remote Stream for Connection 1</td>
787 <td>Remote Stream for Connection 2</td>
788 <td>Remote Stream for Connection 3</td>
789 <td>Remote Stream for Connection 4</td>
792 <td><video width="320" height="240" id="local-view"
793 autoplay="autoplay"></video></td>
794 <td><video width="320" height="240" id="remote-view-1"
795 autoplay="autoplay"></video></td>
796 <td><video width="320" height="240" id="remote-view-2"
797 autoplay="autoplay"></video></td>
798 <td><video width="320" height="240" id="remote-view-3"
799 autoplay="autoplay"></video></td>
800 <td><video width="320" height="240" id="remote-view-4"
801 autoplay="autoplay"></video></td>
802 <!-- Canvases are named after their corresponding video elements. -->
803 <td><canvas width="320" height="240" id="remote-view-1-canvas"
804 style="display:none"></canvas></td>
805 <td><canvas width="320" height="240" id="remote-view-2-canvas"
806 style="display:none"></canvas></td>
807 <td><canvas width="320" height="240" id="remote-view-3-canvas"
808 style="display:none"></canvas></td>
809 <td><canvas width="320" height="240" id="remote-view-4-canvas"
810 style="display:none"></canvas></td>