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