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) { failTest(error); };
23 var onRemoteDescriptionError = function(error) { failTest(error); };
25 // Temporary measure to be able to force iSAC 16K where needed, particularly
26 // on Android. This applies to every test which is why it's implemented like
28 var maybeForceIsac16K = function(sdp) { return sdp; };
29 function forceIsac16KInSdp() {
30 maybeForceIsac16K = function(sdp) {
31 sdp = sdp.replace(/m=audio (\d+) RTP\/SAVPF.*\r\n/g,
32 'm=audio $1 RTP/SAVPF 103 126\r\n');
33 sdp = sdp.replace('a=fmtp:111 minptime=10', 'a=fmtp:103 minptime=10');
34 if (sdp.search('a=rtpmap:103 ISAC/16000') == -1)
35 failTest('Missing iSAC 16K codec on Android; cannot force codec.');
38 sendValueToTest('isac-forced');
41 // When using external SDES, the crypto key is chosen by javascript.
42 var EXTERNAL_SDES_LINES = {
43 'audio': 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' +
44 'inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR',
45 'video': 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' +
46 'inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj',
47 'data': 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' +
48 'inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj'
51 // When using GICE, the ICE credentials can be chosen by javascript.
52 var EXTERNAL_GICE_UFRAG = '1234567890123456';
53 var EXTERNAL_GICE_PWD = '123456789012345678901234';
55 setAllEventsOccuredHandler(reportTestSuccess);
57 // Test that we can setup call with an audio and video track.
58 function call(constraints) {
59 createConnections(null);
60 navigator.webkitGetUserMedia(constraints,
61 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
62 waitForVideo('remote-view-1');
63 waitForVideo('remote-view-2');
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 // First makes a call between pc1 and pc2, and then makes a call between pc3
99 // and pc4. The stream sent from pc3 to pc4 is the stream received on pc1.
100 // The stream sent from pc4 to pc3 is cloned from the stream received on pc2
101 // to test that cloning of remote video tracks works as intended.
102 function callAndForwardRemoteStream(constraints) {
103 createConnections(null);
104 navigator.webkitGetUserMedia(constraints,
105 addStreamToBothConnectionsAndNegotiate,
106 printGetUserMediaError);
107 var gotRemoteStream1 = false;
108 var gotRemoteStream2 = false;
110 var onRemoteStream1 = function() {
111 gotRemoteStream1 = true;
112 maybeCallEstablished();
115 var onRemoteStream2 = function() {
116 gotRemoteStream2 = true;
117 maybeCallEstablished();
120 var maybeCallEstablished = function() {
121 if (gotRemoteStream1 && gotRemoteStream2) {
126 var onCallEstablished = function() {
127 thirdConnection = createConnection(null, 'remote-view-3');
128 thirdConnection.addStream(gRemoteStreams['remote-view-1']);
130 fourthConnection = createConnection(null, 'remote-view-4');
131 fourthConnection.addStream(gRemoteStreams['remote-view-2'].clone());
133 negotiateBetween(thirdConnection, fourthConnection);
135 waitForVideo('remote-view-3');
136 waitForVideo('remote-view-4');
139 // Do the forwarding after we have received video.
140 detectVideoPlaying('remote-view-1', onRemoteStream1);
141 detectVideoPlaying('remote-view-2', onRemoteStream2);
144 // Test that we can setup call with an audio and video track and
145 // simulate that the remote peer don't support MSID.
146 function callWithoutMsidAndBundle() {
147 createConnections(null);
148 transformSdp = removeBundle;
149 transformRemoteSdp = removeMsid;
150 gTestWithoutMsid = true;
151 navigator.webkitGetUserMedia({audio: true, video: true},
152 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
153 waitForVideo('remote-view-1');
154 waitForVideo('remote-view-2');
157 // Test that we can't setup a call with an unsupported video codec
158 function negotiateUnsupportedVideoCodec() {
159 createConnections(null);
160 transformSdp = removeVideoCodec;
162 onLocalDescriptionError = function(error) {
163 var expectedMsg = 'Failed to set local offer sdp:' +
164 ' Session error code: ERROR_CONTENT. Session error description:' +
165 ' Failed to set video receive codecs..';
166 assertEquals(expectedMsg, error);
169 navigator.webkitGetUserMedia({audio: true, video: true},
170 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
173 // Test that we can't setup a call if one peer does not support encryption
174 function negotiateNonCryptoCall() {
175 createConnections(null);
176 transformSdp = removeCrypto;
177 onLocalDescriptionError = function(error) {
178 var expectedMsg = 'Failed to set local offer sdp:' +
179 ' Called with SDP without DTLS fingerprint.';
181 assertEquals(expectedMsg, error);
184 navigator.webkitGetUserMedia({audio: true, video: true},
185 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
188 // Test that we can negotiate a call with an SDP offer that includes a
189 // b=AS:XX line to control audio and video bandwidth
190 function negotiateOfferWithBLine() {
191 createConnections(null);
192 transformSdp = addBandwithControl;
193 navigator.webkitGetUserMedia({audio: true, video: true},
194 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
195 waitForVideo('remote-view-1');
196 waitForVideo('remote-view-2');
199 // Test that we can setup call with legacy settings.
200 function callWithLegacySdp() {
201 transformSdp = function(sdp) {
202 return removeBundle(useGice(useExternalSdes(sdp)));
204 transformCandidate = addGiceCredsToCandidate;
206 'mandatory': {'RtpDataChannels': true, 'DtlsSrtpKeyAgreement': false}
208 setupDataChannel({reliable: false});
209 navigator.webkitGetUserMedia({audio: true, video: true},
210 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
211 waitForVideo('remote-view-1');
212 waitForVideo('remote-view-2');
215 // Test only a data channel.
216 function callWithDataOnly() {
217 createConnections({optional:[{RtpDataChannels: true}]});
218 setupDataChannel({reliable: false});
222 function callWithSctpDataOnly() {
223 createConnections({optional: [{DtlsSrtpKeyAgreement: true}]});
224 setupSctpDataChannel({reliable: true});
228 // Test call with audio, video and a data channel.
229 function callWithDataAndMedia() {
230 createConnections({optional:[{RtpDataChannels: true}]});
231 setupDataChannel({reliable: false});
232 navigator.webkitGetUserMedia({audio: true, video: true},
233 addStreamToBothConnectionsAndNegotiate,
234 printGetUserMediaError);
235 waitForVideo('remote-view-1');
236 waitForVideo('remote-view-2');
239 function callWithSctpDataAndMedia() {
240 createConnections({optional: [{DtlsSrtpKeyAgreement: true}]});
241 setupSctpDataChannel({reliable: true});
242 navigator.webkitGetUserMedia({audio: true, video: true},
243 addStreamToBothConnectionsAndNegotiate,
244 printGetUserMediaError);
245 waitForVideo('remote-view-1');
246 waitForVideo('remote-view-2');
250 // Test call with a data channel and later add audio and video.
251 function callWithDataAndLaterAddMedia() {
252 createConnections({optional:[{RtpDataChannels: true}]});
253 setupDataChannel({reliable: false});
256 // Set an event handler for when the data channel has been closed.
257 setAllEventsOccuredHandler(function() {
258 // When the video is flowing the test is done.
259 setAllEventsOccuredHandler(reportTestSuccess);
260 navigator.webkitGetUserMedia({audio: true, video: true},
261 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
262 waitForVideo('remote-view-1');
263 waitForVideo('remote-view-2');
267 // Test that we can setup call and send DTMF.
268 function callAndSendDtmf(tones) {
269 createConnections(null);
270 navigator.webkitGetUserMedia({audio: true, video: true},
271 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
272 var onCallEstablished = function() {
274 var localAudioTrack = gLocalStream.getAudioTracks()[0];
275 var dtmfSender = gFirstConnection.createDTMFSender(localAudioTrack);
276 dtmfSender.ontonechange = onToneChange;
277 dtmfSender.insertDTMF(tones);
278 // Wait for the DTMF tones callback.
280 var waitDtmf = setInterval(function() {
281 if (gSentTones == tones) {
282 clearInterval(waitDtmf);
288 // Do the DTMF test after we have received video.
289 detectVideoPlaying('remote-view-2', onCallEstablished);
292 function enableRemoteVideo(peerConnection, enabled) {
293 remoteStream = peerConnection.getRemoteStreams()[0];
294 remoteVideoTrack = remoteStream.getVideoTracks()[0];
295 remoteVideoTrack.enabled = enabled;
298 function enableRemoteAudio(peerConnection, enabled) {
299 remoteStream = peerConnection.getRemoteStreams()[0];
300 remoteAudioTrack = remoteStream.getAudioTracks()[0];
301 remoteAudioTrack.enabled = enabled;
304 function callAndEnsureAudioIsPlaying(beLenient, constraints) {
305 createConnections(null);
306 navigator.webkitGetUserMedia(constraints,
307 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
309 // Wait until we have gathered samples and can conclude if audio is playing.
311 var onCallEstablished = function() {
312 // Gather 50 samples per second for 2 seconds.
313 gatherAudioLevelSamples(gSecondConnection, 100, 50, function(samples) {
314 verifyAudioIsPlaying(samples, beLenient);
319 waitForConnectionToStabilize(gFirstConnection, onCallEstablished);
322 function callAndEnsureAudioTrackMutingWorks(beLenient) {
323 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
324 setAllEventsOccuredHandler(function() {
325 // Call is up, now mute the track and check everything goes silent (give
326 // it a small delay though, we don't expect it to happen instantly).
327 enableRemoteAudio(gSecondConnection, false);
329 setTimeout(function() {
330 gatherAudioLevelSamples(gSecondConnection, 100, 50, function(samples) {
331 verifyIsSilent(samples);
338 function callAndEnsureAudioTrackUnmutingWorks(beLenient) {
339 callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
340 setAllEventsOccuredHandler(function() {
341 // Mute, wait a while, unmute, verify audio gets back up.
342 // (Also, ensure video muting doesn't affect audio).
343 enableRemoteAudio(gSecondConnection, false);
344 enableRemoteVideo(gSecondConnection, false);
346 setTimeout(function() {
347 enableRemoteAudio(gSecondConnection, true);
350 setTimeout(function() {
351 // Sample for four seconds here; it can take a bit of time for audio to
352 // get back up after the unmute.
353 gatherAudioLevelSamples(gSecondConnection, 200, 50, function(samples) {
354 verifyAudioIsPlaying(samples, beLenient);
361 function callAndEnsureVideoTrackMutingWorks() {
362 createConnections(null);
363 navigator.webkitGetUserMedia({audio: true, video: true},
364 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
367 detectVideoPlaying('remote-view-2', function() {
368 // Disable the receiver's remote media stream. Video should stop.
369 // (Also, ensure muting audio doesn't affect video).
370 enableRemoteVideo(gSecondConnection, false);
371 enableRemoteAudio(gSecondConnection, false);
373 detectVideoStopped('remote-view-2', function() {
374 // Video has stopped: unmute and succeed if it starts playing again.
375 enableRemoteVideo(gSecondConnection, true);
376 detectVideoPlaying('remote-view-2', eventOccured);
381 // Test call with a new Video MediaStream that has been created based on a
382 // stream generated by getUserMedia.
383 function callWithNewVideoMediaStream() {
384 createConnections(null);
385 navigator.webkitGetUserMedia({audio: true, video: true},
386 createNewVideoStreamAndAddToBothConnections, printGetUserMediaError);
387 waitForVideo('remote-view-1');
388 waitForVideo('remote-view-2');
391 // Test call with a new Video MediaStream that has been created based on a
392 // stream generated by getUserMedia. When Video is flowing, an audio track
393 // is added to the sent stream and the video track is removed. This
394 // is to test that adding and removing of remote tracks on an existing
395 // mediastream works.
396 function callWithNewVideoMediaStreamLaterSwitchToAudio() {
397 createConnections(null);
398 navigator.webkitGetUserMedia({audio: true, video: true},
399 createNewVideoStreamAndAddToBothConnections, printGetUserMediaError);
401 waitForVideo('remote-view-1');
402 waitForVideo('remote-view-2');
404 // Set an event handler for when video is playing.
405 setAllEventsOccuredHandler(function() {
406 // Add an audio track to the local stream and remove the video track and
407 // then renegotiate. But first - setup the expectations.
408 local_stream = gFirstConnection.getLocalStreams()[0];
410 remote_stream_1 = gFirstConnection.getRemoteStreams()[0];
411 // Add an expected event that onaddtrack will be called on the remote
412 // mediastream received on gFirstConnection when the audio track is
415 remote_stream_1.onaddtrack = function(){
416 assertEquals(remote_stream_1.getAudioTracks()[0].id,
417 local_stream.getAudioTracks()[0].id);
421 // Add an expectation that the received video track is removed from
424 remote_stream_1.onremovetrack = function() {
428 // Add an expected event that onaddtrack will be called on the remote
429 // mediastream received on gSecondConnection when the audio track is
431 remote_stream_2 = gSecondConnection.getRemoteStreams()[0];
433 remote_stream_2.onaddtrack = function() {
434 assertEquals(remote_stream_2.getAudioTracks()[0].id,
435 local_stream.getAudioTracks()[0].id);
439 // Add an expectation that the received video track is removed from
440 // gSecondConnection.
442 remote_stream_2.onremovetrack = function() {
445 // When all the above events have occurred- the test pass.
446 setAllEventsOccuredHandler(reportTestSuccess);
448 local_stream.addTrack(gLocalStream.getAudioTracks()[0]);
449 local_stream.removeTrack(local_stream.getVideoTracks()[0]);
454 // This function is used for setting up a test that:
455 // 1. Creates a data channel on |gFirstConnection| and sends data to
456 // |gSecondConnection|.
457 // 2. When data is received on |gSecondConnection| a message
458 // is sent to |gFirstConnection|.
459 // 3. When data is received on |gFirstConnection|, the data
460 // channel is closed. The test passes when the state transition completes.
461 function setupDataChannel(params) {
462 var sendDataString = "send some text on a data channel."
463 firstDataChannel = gFirstConnection.createDataChannel(
464 "sendDataChannel", params);
465 assertEquals('connecting', firstDataChannel.readyState);
467 // When |firstDataChannel| transition to open state, send a text string.
468 firstDataChannel.onopen = function() {
469 assertEquals('open', firstDataChannel.readyState);
470 if (firstDataChannel.reliable) {
471 firstDataChannel.send(sendDataString);
473 sendDataRepeatedlyUntilClosed(firstDataChannel);
477 // When |firstDataChannel| receive a message, close the channel and
478 // initiate a new offer/answer exchange to complete the closure.
479 firstDataChannel.onmessage = function(event) {
480 assertEquals(event.data, sendDataString);
481 firstDataChannel.close();
485 // When |firstDataChannel| transition to closed state, the test pass.
487 firstDataChannel.onclose = function() {
488 assertEquals('closed', firstDataChannel.readyState);
492 // Event handler for when |gSecondConnection| receive a new dataChannel.
493 gSecondConnection.ondatachannel = function (event) {
494 var secondDataChannel = event.channel;
496 // When |secondDataChannel| receive a message, send a message back.
497 secondDataChannel.onmessage = function(event) {
498 assertEquals(event.data, sendDataString);
499 console.log("gSecondConnection received data");
500 if (secondDataChannel.reliable) {
501 // If we're reliable we will just send one message over the channel,
502 // and therefore channel one's message handler cannot have shut us
504 assertEquals('open', secondDataChannel.readyState);
505 secondDataChannel.send(sendDataString);
507 // If unreliable, this could be one in a series of messages and it
508 // is possible we already replied (which will close our channel).
509 sendDataRepeatedlyUntilClosed(secondDataChannel);
514 // Sends |sendDataString| on |dataChannel| every 200ms as long as
515 // |dataChannel| is open.
516 function sendDataRepeatedlyUntilClosed(dataChannel) {
517 var sendTimer = setInterval(function() {
518 if (dataChannel.readyState == 'open')
519 dataChannel.send(sendDataString);
521 clearInterval(sendTimer);
526 // SCTP data channel setup is slightly different then RTP based
527 // channels. Due to a bug in libjingle, we can't send data immediately
528 // after channel becomes open. So for that reason in SCTP,
529 // we are sending data from second channel, when ondatachannel event is
530 // received. So data flow happens 2 -> 1 -> 2.
531 function setupSctpDataChannel(params) {
532 var sendDataString = "send some text on a data channel."
533 firstDataChannel = gFirstConnection.createDataChannel(
534 "sendDataChannel", params);
535 assertEquals('connecting', firstDataChannel.readyState);
537 // When |firstDataChannel| transition to open state, send a text string.
538 firstDataChannel.onopen = function() {
539 assertEquals('open', firstDataChannel.readyState);
542 // When |firstDataChannel| receive a message, send message back.
543 // initiate a new offer/answer exchange to complete the closure.
544 firstDataChannel.onmessage = function(event) {
545 assertEquals('open', firstDataChannel.readyState);
546 assertEquals(event.data, sendDataString);
547 firstDataChannel.send(sendDataString);
551 // Event handler for when |gSecondConnection| receive a new dataChannel.
552 gSecondConnection.ondatachannel = function (event) {
553 var secondDataChannel = event.channel;
554 secondDataChannel.onopen = function() {
555 secondDataChannel.send(sendDataString);
558 // When |secondDataChannel| receive a message, close the channel and
559 // initiate a new offer/answer exchange to complete the closure.
560 secondDataChannel.onmessage = function(event) {
561 assertEquals(event.data, sendDataString);
562 assertEquals('open', secondDataChannel.readyState);
563 secondDataChannel.close();
567 // When |secondDataChannel| transition to closed state, the test pass.
569 secondDataChannel.onclose = function() {
570 assertEquals('closed', secondDataChannel.readyState);
576 // Test call with a stream that has been created by getUserMedia, clone
577 // the stream to a cloned stream, send them via the same peer connection.
578 function addTwoMediaStreamsToOneConnection() {
579 createConnections(null);
580 navigator.webkitGetUserMedia({audio: true, video: true},
581 CloneStreamAndAddTwoStreamstoOneConnection, printGetUserMediaError);
584 function onToneChange(tone) {
585 gSentTones += tone.tone;
588 function createConnections(constraints) {
589 gFirstConnection = createConnection(constraints, 'remote-view-1');
590 assertEquals('stable', gFirstConnection.signalingState);
592 gSecondConnection = createConnection(constraints, 'remote-view-2');
593 assertEquals('stable', gSecondConnection.signalingState);
596 function createConnection(constraints, remoteView) {
597 var pc = new webkitRTCPeerConnection(null, constraints);
598 pc.onaddstream = function(event) {
599 onRemoteStream(event, remoteView);
604 function displayAndRemember(localStream) {
605 var localStreamUrl = URL.createObjectURL(localStream);
606 $('local-view').src = localStreamUrl;
608 gLocalStream = localStream;
611 // Called if getUserMedia fails.
612 function printGetUserMediaError(error) {
613 var message = 'getUserMedia request unexpectedly failed:';
614 if (error.constraintName)
615 message += ' could not satisfy constraint ' + error.constraintName;
617 message += ' devices not working/user denied access.';
621 // Called if getUserMedia succeeds and we want to send from both connections.
622 function addStreamToBothConnectionsAndNegotiate(localStream) {
623 displayAndRemember(localStream);
624 gFirstConnection.addStream(localStream);
625 gSecondConnection.addStream(localStream);
629 // Called if getUserMedia succeeds when we want to send from one connection.
630 function addStreamToTheFirstConnectionAndNegotiate(localStream) {
631 displayAndRemember(localStream);
632 gFirstConnection.addStream(localStream);
636 function verifyHasOneAudioAndVideoTrack(stream) {
637 assertEquals(1, stream.getAudioTracks().length);
638 assertEquals(1, stream.getVideoTracks().length);
641 // Called if getUserMedia succeeds, then clone the stream, send two streams
642 // from one peer connection.
643 function CloneStreamAndAddTwoStreamstoOneConnection(localStream) {
644 displayAndRemember(localStream);
646 var clonedStream = null;
647 if (typeof localStream.clone === "function") {
648 clonedStream = localStream.clone();
650 clonedStream = new webkitMediaStream(localStream);
653 gFirstConnection.addStream(localStream);
654 gFirstConnection.addStream(clonedStream);
656 // Verify the local streams are correct.
657 assertEquals(2, gFirstConnection.getLocalStreams().length);
658 verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[0]);
659 verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[1]);
661 // The remote side should receive two streams. After that, verify the
662 // remote side has the correct number of streams and tracks.
665 gSecondConnection.onaddstream = function(event) {
668 setAllEventsOccuredHandler(function() {
669 // Negotiation complete, verify remote streams on the receiving side.
670 assertEquals(2, gSecondConnection.getRemoteStreams().length);
671 verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[0]);
672 verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[1]);
680 // Called if getUserMedia succeeds when we want to send a modified
681 // MediaStream. A new MediaStream is created and the video track from
682 // |localStream| is added.
683 function createNewVideoStreamAndAddToBothConnections(localStream) {
684 displayAndRemember(localStream);
685 var new_stream = new webkitMediaStream();
686 new_stream.addTrack(localStream.getVideoTracks()[0]);
687 gFirstConnection.addStream(new_stream);
688 gSecondConnection.addStream(new_stream);
692 function negotiate() {
693 negotiateBetween(gFirstConnection, gSecondConnection);
696 function negotiateBetween(caller, callee) {
697 console.log("Negotiating call...");
698 // Not stable = negotiation is ongoing. The behavior of re-negotiating while
699 // a negotiation is ongoing is more or less undefined, so avoid this.
700 if (caller.signalingState != 'stable' || callee.signalingState != 'stable')
701 throw 'You can only negotiate when the connection is stable!';
703 connectOnIceCandidate(caller, callee);
707 onOfferCreated(offer, caller, callee);
711 function onOfferCreated(offer, caller, callee) {
712 offer.sdp = maybeForceIsac16K(transformSdp(offer.sdp));
713 caller.setLocalDescription(offer, function() {
714 assertEquals('have-local-offer', caller.signalingState);
715 receiveOffer(offer.sdp, caller, callee);
716 }, onLocalDescriptionError);
719 function receiveOffer(offerSdp, caller, callee) {
720 console.log("Receiving offer...");
721 offerSdp = transformRemoteSdp(offerSdp);
723 var parsedOffer = new RTCSessionDescription({ type: 'offer',
725 callee.setRemoteDescription(parsedOffer, function() {},
726 onRemoteDescriptionError);
727 callee.createAnswer(function (answer) {
728 onAnswerCreated(answer, caller, callee);
730 assertEquals('have-remote-offer', callee.signalingState);
733 function removeMsid(offerSdp) {
734 offerSdp = offerSdp.replace(/a=msid-semantic.*\r\n/g, '');
735 offerSdp = offerSdp.replace('a=mid:audio\r\n', '');
736 offerSdp = offerSdp.replace('a=mid:video\r\n', '');
737 offerSdp = offerSdp.replace(/a=ssrc.*\r\n/g, '');
741 function removeVideoCodec(offerSdp) {
742 offerSdp = offerSdp.replace('a=rtpmap:100 VP8/90000\r\n',
743 'a=rtpmap:100 XVP8/90000\r\n');
747 function removeCrypto(offerSdp) {
748 offerSdp = offerSdp.replace(/a=crypto.*\r\n/g, 'a=Xcrypto\r\n');
749 offerSdp = offerSdp.replace(/a=fingerprint.*\r\n/g, '');
753 function addBandwithControl(offerSdp) {
754 offerSdp = offerSdp.replace('a=mid:audio\r\n', 'a=mid:audio\r\n'+
756 offerSdp = offerSdp.replace('a=mid:video\r\n', 'a=mid:video\r\n'+
761 function removeBundle(sdp) {
762 return sdp.replace(/a=group:BUNDLE .*\r\n/g, '');
765 function useGice(sdp) {
766 sdp = sdp.replace(/t=.*\r\n/g, function(subString) {
767 return subString + 'a=ice-options:google-ice\r\n';
769 sdp = sdp.replace(/a=ice-ufrag:.*\r\n/g,
770 'a=ice-ufrag:' + EXTERNAL_GICE_UFRAG + '\r\n');
771 sdp = sdp.replace(/a=ice-pwd:.*\r\n/g,
772 'a=ice-pwd:' + EXTERNAL_GICE_PWD + '\r\n');
776 function useExternalSdes(sdp) {
777 // Remove current crypto specification.
778 sdp = sdp.replace(/a=crypto.*\r\n/g, '');
779 sdp = sdp.replace(/a=fingerprint.*\r\n/g, '');
780 // Add external crypto. This is not compatible with |removeMsid|.
781 sdp = sdp.replace(/a=mid:(\w+)\r\n/g, function(subString, group) {
782 return subString + EXTERNAL_SDES_LINES[group] + '\r\n';
787 function onAnswerCreated(answer, caller, callee) {
788 answer.sdp = maybeForceIsac16K(transformSdp(answer.sdp));
789 callee.setLocalDescription(answer,
791 assertEquals('stable', callee.signalingState);
793 onLocalDescriptionError);
794 receiveAnswer(answer.sdp, caller);
797 function receiveAnswer(answerSdp, caller) {
798 console.log("Receiving answer...");
799 answerSdp = transformRemoteSdp(answerSdp);
800 var parsedAnswer = new RTCSessionDescription({ type: 'answer',
802 caller.setRemoteDescription(parsedAnswer,
804 assertEquals('stable', caller.signalingState);
806 onRemoteDescriptionError);
809 function connectOnIceCandidate(caller, callee) {
810 caller.onicecandidate = function(event) { onIceCandidate(event, callee); }
811 callee.onicecandidate = function(event) { onIceCandidate(event, caller); }
814 function addGiceCredsToCandidate(candidate) {
815 return candidate.trimRight() +
816 ' username ' + EXTERNAL_GICE_UFRAG + ' password ' + EXTERNAL_GICE_PWD;
819 function onIceCandidate(event, target) {
820 if (event.candidate) {
821 var candidate = new RTCIceCandidate(event.candidate);
822 candidate.candidate = transformCandidate(candidate.candidate);
823 target.addIceCandidate(candidate);
827 function onRemoteStream(e, target) {
828 console.log("Receiving remote stream...");
829 if (gTestWithoutMsid && e.stream.id != "default") {
830 failTest('a default remote stream was expected but instead ' +
831 e.stream.id + ' was received.');
833 gRemoteStreams[target] = e.stream;
834 var remoteStreamUrl = URL.createObjectURL(e.stream);
835 var remoteVideo = $(target);
836 remoteVideo.src = remoteStreamUrl;
844 <td>Local Preview</td>
845 <td>Remote Stream for Connection 1</td>
846 <td>Remote Stream for Connection 2</td>
847 <td>Remote Stream for Connection 3</td>
848 <td>Remote Stream for Connection 4</td>
851 <td><video width="320" height="240" id="local-view"
852 autoplay="autoplay"></video></td>
853 <td><video width="320" height="240" id="remote-view-1"
854 autoplay="autoplay"></video></td>
855 <td><video width="320" height="240" id="remote-view-2"
856 autoplay="autoplay"></video></td>
857 <td><video width="320" height="240" id="remote-view-3"
858 autoplay="autoplay"></video></td>
859 <td><video width="320" height="240" id="remote-view-4"
860 autoplay="autoplay"></video></td>
861 <!-- Canvases are named after their corresponding video elements. -->
862 <td><canvas width="320" height="240" id="remote-view-1-canvas"
863 style="display:none"></canvas></td>
864 <td><canvas width="320" height="240" id="remote-view-2-canvas"
865 style="display:none"></canvas></td>
866 <td><canvas width="320" height="240" id="remote-view-3-canvas"
867 style="display:none"></canvas></td>
868 <td><canvas width="320" height="240" id="remote-view-4-canvas"
869 style="display:none"></canvas></td>