1 <html> 2 <head> 3 <script type="text/javascript" src="webrtc_test_utilities.js"></script> 4 <script type="text/javascript"> 5 $ = function(id) { 6 return document.getElementById(id); 7 }; 8 9 var gFirstConnection = null; 10 var gSecondConnection = null; 11 var gTestWithoutMsidAndBundle = false; 12 13 var gLocalStream = null; 14 var gSentTones = ''; 15 16 setAllEventsOccuredHandler(function() { 17 document.title = 'OK'; 18 }); 19 20 // Test that we can setup call with an audio and video track. 21 function call(constraints) { 22 createConnections(null); 23 navigator.webkitGetUserMedia(constraints, 24 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError); 25 waitForVideo('remote-view-1'); 26 waitForVideo('remote-view-2'); 27 } 28 29 // First calls without streams on any connections, and then adds a stream 30 // to peer connection 1 which gets sent to peer connection 2. We must wait 31 // for the first negotiation to complete before starting the second one, which 32 // is why we wait until the connection is stable before re-negotiating. 33 function callEmptyThenAddOneStreamAndRenegotiate(constraints) { 34 createConnections(null); 35 negotiate(); 36 waitForConnectionToStabilize(gFirstConnection); 37 navigator.webkitGetUserMedia(constraints, 38 addStreamToTheFirstConnectionAndNegotiate, printGetUserMediaError); 39 // Only the first connection is sending here. 40 waitForVideo('remote-view-2'); 41 } 42 43 // Test that we can setup call with an audio and video track and 44 // simulate that the remote peer don't support MSID. 45 function callWithoutMsidAndBundle() { 46 createConnections(null); 47 gTestWithoutMsidAndBundle = true; 48 navigator.webkitGetUserMedia({audio:true, video:true}, 49 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError); 50 waitForVideo('remote-view-1'); 51 waitForVideo('remote-view-2'); 52 } 53 54 // Test only a data channel. 55 function callWithDataOnly() { 56 createConnections({optional:[{RtpDataChannels: true}]}); 57 setupDataChannel(); 58 negotiate(); 59 } 60 61 // Test call with audio, video and a data channel. 62 function callWithDataAndMedia() { 63 createConnections({optional:[{RtpDataChannels: true}]}); 64 setupDataChannel(); 65 navigator.webkitGetUserMedia({audio:true, video:true}, 66 addStreamToBothConnectionsAndNegotiate, 67 printGetUserMediaError); 68 waitForVideo('remote-view-1'); 69 waitForVideo('remote-view-2'); 70 } 71 72 // Test call with a data channel and later add audio and video. 73 function callWithDataAndLaterAddMedia() { 74 createConnections({optional:[{RtpDataChannels: true}]}); 75 setupDataChannel(); 76 negotiate(); 77 78 // Set an event handler for when the data channel has been closed. 79 setAllEventsOccuredHandler(function() { 80 // When the video is flowing the test is done. 81 setAllEventsOccuredHandler(function() { 82 document.title = 'OK'; 83 }); 84 navigator.webkitGetUserMedia({audio:true, video:true}, 85 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError); 86 waitForVideo('remote-view-1'); 87 waitForVideo('remote-view-2'); 88 }); 89 } 90 91 // Test that we can setup call and send DTMF. 92 function callAndSendDtmf(tones) { 93 createConnections(null); 94 navigator.webkitGetUserMedia({audio:true, video:true}, 95 addStreamToBothConnectionsAndNegotiate, printGetUserMediaError); 96 var onCallEstablished = function() { 97 // Send DTMF tones. 98 var localAudioTrack = gLocalStream.getAudioTracks()[0]; 99 var dtmfSender = gFirstConnection.createDTMFSender(localAudioTrack); 100 dtmfSender.ontonechange = onToneChange; 101 dtmfSender.insertDTMF(tones); 102 // Wait for the DTMF tones callback. 103 document.title = 'Waiting for dtmf...'; 104 addExpectedEvent(); 105 var waitDtmf = setInterval(function() { 106 if (gSentTones == tones) { 107 clearInterval(waitDtmf); 108 eventOccured(); 109 } 110 }, 100); 111 } 112 113 // Do the DTMF test after we have received video. 114 detectVideoIn('remote-view-2', onCallEstablished); 115 } 116 117 // Test call with a new Video MediaStream that has been created based on a 118 // stream generated by getUserMedia. 119 function callWithNewVideoMediaStream() { 120 createConnections(null); 121 navigator.webkitGetUserMedia({audio:true, video:true}, 122 createNewVideoStreamAndAddToBothConnections, printGetUserMediaError); 123 waitForVideo('remote-view-1'); 124 waitForVideo('remote-view-2'); 125 } 126 127 // Test call with a new Video MediaStream that has been created based on a 128 // stream generated by getUserMedia. When Video is flowing, an audio track 129 // is added to the sent stream and the video track is removed. This 130 // is to test that adding and removing of remote tracks on an existing 131 // mediastream works. 132 function callWithNewVideoMediaStreamLaterSwitchToAudio() { 133 createConnections(null); 134 navigator.webkitGetUserMedia({audio:true, video:true}, 135 createNewVideoStreamAndAddToBothConnections, printGetUserMediaError); 136 137 waitForVideo('remote-view-1'); 138 waitForVideo('remote-view-2'); 139 140 // Set an event handler for when video is playing. 141 setAllEventsOccuredHandler(function() { 142 // Add an audio track to the local stream and remove the video track and 143 // then renegotiate. But first - setup the expectations. 144 local_stream = gFirstConnection.getLocalStreams()[0]; 145 146 remote_stream_1 = gFirstConnection.getRemoteStreams()[0]; 147 // Add an expected event that onaddtrack will be called on the remote 148 // mediastream received on gFirstConnection when the audio track is 149 // received. 150 addExpectedEvent(); 151 remote_stream_1.onaddtrack = function(){ 152 expectEquals(remote_stream_1.getAudioTracks()[0].id, 153 local_stream.getAudioTracks()[0].id); 154 eventOccured(); 155 } 156 157 // Add an expectation that the received video track is removed from 158 // gFirstConnection. 159 addExpectedEvent(); 160 remote_stream_1.onremovetrack = function() { 161 eventOccured(); 162 } 163 164 // Add an expected event that onaddtrack will be called on the remote 165 // mediastream received on gSecondConnection when the audio track is 166 // received. 167 remote_stream_2 = gSecondConnection.getRemoteStreams()[0]; 168 addExpectedEvent(); 169 remote_stream_2.onaddtrack = function() { 170 expectEquals(remote_stream_2.getAudioTracks()[0].id, 171 local_stream.getAudioTracks()[0].id); 172 eventOccured(); 173 } 174 175 // Add an expectation that the received video track is removed from 176 // gSecondConnection. 177 addExpectedEvent(); 178 remote_stream_2.onremovetrack = function() { 179 eventOccured(); 180 } 181 // When all the above events have occurred- the test pass. 182 setAllEventsOccuredHandler(function() { document.title = 'OK'; }); 183 184 local_stream.addTrack(gLocalStream.getAudioTracks()[0]); 185 local_stream.removeTrack(local_stream.getVideoTracks()[0]); 186 negotiate(); 187 }); // End of setAllEventsOccuredHandler. 188 } 189 190 // This function is used for setting up a test that: 191 // 1. Creates a data channel on |gFirstConnection| and sends data to 192 // |gSecondConnection|. 193 // 2. When data is received on |gSecondConnection| a message 194 // is sent to |gFirstConnection|. 195 // 3. When data is received on |gFirstConnection|, the data 196 // channel is closed. The test passes when the state transition completes. 197 function setupDataChannel() { 198 var sendDataString = "send some text on a data channel." 199 firstDataChannel = gFirstConnection.createDataChannel( 200 "sendDataChannel", {reliable : false}); 201 expectEquals('connecting', firstDataChannel.readyState); 202 203 // When |firstDataChannel| transition to open state, send a text string. 204 firstDataChannel.onopen = function() { 205 expectEquals('open', firstDataChannel.readyState); 206 firstDataChannel.send(sendDataString); 207 } 208 209 // When |firstDataChannel| receive a message, close the channel and 210 // initiate a new offer/answer exchange to complete the closure. 211 firstDataChannel.onmessage = function(event) { 212 expectEquals(event.data, sendDataString); 213 firstDataChannel.close(); 214 negotiate(); 215 } 216 217 // When |firstDataChannel| transition to closed state, the test pass. 218 addExpectedEvent(); 219 firstDataChannel.onclose = function() { 220 expectEquals('closed', firstDataChannel.readyState); 221 eventOccured(); 222 } 223 224 // Event handler for when |gSecondConnection| receive a new dataChannel. 225 gSecondConnection.ondatachannel = function (event) { 226 var secondDataChannel = event.channel; 227 228 // When |secondDataChannel| receive a message, send a message back. 229 secondDataChannel.onmessage = function(event) { 230 expectEquals(event.data, sendDataString); 231 expectEquals('open', secondDataChannel.readyState); 232 secondDataChannel.send(sendDataString); 233 } 234 } 235 } 236 237 // Test call with a stream that has been created by getUserMedia, clone 238 // the stream to a cloned stream, send them via the same peer connection. 239 function addTwoMediaStreamsToOneConnection() { 240 createConnections(null); 241 navigator.webkitGetUserMedia({audio:true, video:true}, 242 CloneStreamAndAddTwoStreamstoOneConnection, printGetUserMediaError); 243 } 244 245 function onToneChange(tone) { 246 gSentTones += tone.tone; 247 document.title = gSentTones; 248 } 249 250 function createConnections(constraints) { 251 gFirstConnection = new webkitRTCPeerConnection(null, constraints); 252 gFirstConnection.onicecandidate = onIceCandidateToFirst; 253 gFirstConnection.onaddstream = function(event) { 254 onRemoteStream(event, 'remote-view-1'); 255 } 256 expectEquals('stable', gFirstConnection.signalingState); 257 258 gSecondConnection = new webkitRTCPeerConnection(null, constraints); 259 gSecondConnection.onicecandidate = onIceCandidateToSecond; 260 gSecondConnection.onaddstream = function(event) { 261 onRemoteStream(event, 'remote-view-2'); 262 } 263 } 264 265 function displayAndRemember(localStream) { 266 var localStreamUrl = webkitURL.createObjectURL(localStream); 267 $('local-view').src = localStreamUrl; 268 269 gLocalStream = localStream; 270 } 271 272 // Called if getUserMedia fails. 273 function printGetUserMediaError(error) { 274 document.title = 'getUserMedia request failed with code ' + error.code; 275 } 276 277 // Called if getUserMedia succeeds and we want to send from both connections. 278 function addStreamToBothConnectionsAndNegotiate(localStream) { 279 displayAndRemember(localStream); 280 gFirstConnection.addStream(localStream); 281 gSecondConnection.addStream(localStream); 282 negotiate(); 283 } 284 285 // Called if getUserMedia succeeds when we want to send from one connection. 286 function addStreamToTheFirstConnectionAndNegotiate(localStream) { 287 displayAndRemember(localStream); 288 gFirstConnection.addStream(localStream); 289 negotiate(); 290 } 291 292 function verifyHasOneAudioAndVideoTrack(stream) { 293 expectEquals(1, stream.getAudioTracks().length); 294 expectEquals(1, stream.getVideoTracks().length); 295 } 296 297 // Called if getUserMedia succeeds, then clone the stream, send two streams 298 // from one peer connection. 299 function CloneStreamAndAddTwoStreamstoOneConnection(localStream) { 300 displayAndRemember(localStream); 301 var clonedStream = new webkitMediaStream(); 302 clonedStream.addTrack(localStream.getVideoTracks()[0]); 303 clonedStream.addTrack(localStream.getAudioTracks()[0]); 304 gFirstConnection.addStream(localStream); 305 gFirstConnection.addStream(clonedStream); 306 307 // Verify the local streams are correct. 308 expectEquals(2, gFirstConnection.getLocalStreams().length); 309 verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[0]); 310 verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[1]); 311 312 // The remote side should receive two streams. After that, verify the 313 // remote side has the correct number of streams and tracks. 314 addExpectedEvent(); 315 addExpectedEvent(); 316 gSecondConnection.onaddstream = function(event) { 317 eventOccured(); 318 } 319 setAllEventsOccuredHandler(function() { 320 // Negotiation complete, verify remote streams on the receiving side. 321 expectEquals(2, gSecondConnection.getRemoteStreams().length); 322 verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[0]); 323 verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[1]); 324 325 document.title = "OK"; 326 }); 327 328 negotiate(); 329 } 330 331 // Called if getUserMedia succeeds when we want to send a modified 332 // MediaStream. A new MediaStream is created and the video track from 333 // |localStream| is added. 334 function createNewVideoStreamAndAddToBothConnections(localStream) { 335 displayAndRemember(localStream); 336 var new_stream = new webkitMediaStream(); 337 new_stream.addTrack(localStream.getVideoTracks()[0]); 338 gFirstConnection.addStream(new_stream); 339 gSecondConnection.addStream(new_stream); 340 negotiate(); 341 } 342 343 function negotiate() { 344 // Not stable = negotiation is ongoing. The behavior of re-negotiating while 345 // a negotiation is ongoing is more or less undefined, so avoid this. 346 if (gFirstConnection.signalingState != 'stable') 347 throw 'You can only negotiate when the connection is stable!'; 348 349 gFirstConnection.createOffer(onOfferCreated); 350 } 351 352 function onOfferCreated(offer) { 353 gFirstConnection.setLocalDescription(offer); 354 expectEquals('have-local-offer', gFirstConnection.signalingState); 355 receiveOffer(offer.sdp); 356 } 357 358 function receiveOffer(offerSdp) { 359 if (gTestWithoutMsidAndBundle) { 360 offerSdp = removeMsidAndBundle(offerSdp); 361 } 362 363 var parsedOffer = new RTCSessionDescription({ type: 'offer', 364 sdp: offerSdp }); 365 gSecondConnection.setRemoteDescription(parsedOffer); 366 gSecondConnection.createAnswer(onAnswerCreated); 367 expectEquals('have-remote-offer', gSecondConnection.signalingState); 368 } 369 370 function removeMsidAndBundle(offerSdp) { 371 offerSdp = offerSdp.replace(/a=msid-semantic.*\r\n/g, ''); 372 offerSdp = offerSdp.replace('a=group:BUNDLE audio video\r\n', ''); 373 offerSdp = offerSdp.replace('a=mid:audio\r\n', ''); 374 offerSdp = offerSdp.replace('a=mid:video\r\n', ''); 375 offerSdp = offerSdp.replace(/a=ssrc.*\r\n/g, ''); 376 return offerSdp; 377 } 378 379 function onAnswerCreated(answer) { 380 gSecondConnection.setLocalDescription(answer); 381 expectEquals('stable', gSecondConnection.signalingState); 382 receiveAnswer(answer.sdp); 383 } 384 385 function receiveAnswer(answerSdp) { 386 if (gTestWithoutMsidAndBundle) { 387 answerSdp = removeMsidAndBundle(answerSdp); 388 } 389 var parsedAnswer = new RTCSessionDescription({ type: 'answer', 390 sdp: answerSdp }); 391 gFirstConnection.setRemoteDescription(parsedAnswer); 392 expectEquals('stable', gFirstConnection.signalingState); 393 } 394 395 function onIceCandidateToFirst(event) { 396 if (event.candidate) { 397 var candidate = new RTCIceCandidate(event.candidate); 398 gSecondConnection.addIceCandidate(candidate); 399 } 400 } 401 402 function onIceCandidateToSecond(event) { 403 if (event.candidate) { 404 var candidate = new RTCIceCandidate(event.candidate); 405 gFirstConnection.addIceCandidate(candidate); 406 } 407 } 408 409 function onRemoteStream(e, target) { 410 if (gTestWithoutMsidAndBundle && e.stream.id != "default") { 411 document.title = 'a default remote stream was expected but instead ' + 412 e.stream.id + ' was received.'; 413 return; 414 } 415 var remoteStreamUrl = webkitURL.createObjectURL(e.stream); 416 var remoteVideo = $(target); 417 remoteVideo.src = remoteStreamUrl; 418 } 419 420 </script> 421 </head> 422 <body> 423 <table border="0"> 424 <tr> 425 <td>Local Preview</td> 426 <td>Remote Stream for Connection 1</td> 427 <td>Remote Stream for Connection 2</td> 428 </tr> 429 <tr> 430 <td><video width="320" height="240" id="local-view" 431 autoplay="autoplay"></video></td> 432 <td><video width="320" height="240" id="remote-view-1" 433 autoplay="autoplay"></video></td> 434 <td><video width="320" height="240" id="remote-view-2" 435 autoplay="autoplay"></video></td> 436 <!-- Canvases are named after their corresponding video elements. --> 437 <td><canvas width="320" height="240" id="remote-view-1-canvas" 438 style="display:none"></canvas></td> 439 <td><canvas width="320" height="240" id="remote-view-2-canvas" 440 style="display:none"></canvas></td> 441 </tr> 442 </table> 443 </body> 444 </html> 445