blob: f979006b954bac2b5636275ebc8a72eaa930f8a9 [file] [log] [blame]
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001<html>
2<head>
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01003 <script type="text/javascript" src="webrtc_test_utilities.js"></script>
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00004 <script type="text/javascript">
5 $ = function(id) {
6 return document.getElementById(id);
7 };
8
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00009 var gFirstConnection = null;
10 var gSecondConnection = null;
11 var gTestWithoutMsidAndBundle = false;
12
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000013 var gLocalStream = null;
14 var gSentTones = '';
15
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010016 setAllEventsOccuredHandler(function() {
17 document.title = 'OK';
18 });
19
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000020 // 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
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010030 // 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) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000034 createConnections(null);
35 negotiate();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010036 waitForConnectionToStabilize(gFirstConnection);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000037 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);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010050 waitForVideo('remote-view-1');
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000051 waitForVideo('remote-view-2');
52 }
53
54 // Test only a data channel.
55 function callWithDataOnly() {
56 createConnections({optional:[{RtpDataChannels: true}]});
57 setupDataChannel();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010058 negotiate();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000059 }
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() {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000074 createConnections({optional:[{RtpDataChannels: true}]});
75 setupDataChannel();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010076 negotiate();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000077
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +010078 // 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 });
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000089 }
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
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100117 // 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
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000190 // 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();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100214 negotiate();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000215 }
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
Ben Murdochbb1529c2013-08-08 10:24:53 +0100237 // 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
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000245 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
Ben Murdochbb1529c2013-08-08 10:24:53 +0100292 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
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100331 // 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
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000343 function negotiate() {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100344 // 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
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000349 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);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100382 receiveAnswer(answer.sdp);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000383 }
384
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100385 function receiveAnswer(answerSdp) {
386 if (gTestWithoutMsidAndBundle) {
387 answerSdp = removeMsidAndBundle(answerSdp);
388 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000389 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) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100410 if (gTestWithoutMsidAndBundle && e.stream.id != "default") {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000411 document.title = 'a default remote stream was expected but instead ' +
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100412 e.stream.id + ' was received.';
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000413 return;
414 }
415 var remoteStreamUrl = webkitURL.createObjectURL(e.stream);
416 var remoteVideo = $(target);
417 remoteVideo.src = remoteStreamUrl;
418 }
419
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000420 </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>
Ben Murdochbb1529c2013-08-08 10:24:53 +0100439 <td><canvas width="320" height="240" id="remote-view-2-canvas"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000440 style="display:none"></canvas></td>
441 </tr>
442 </table>
443</body>
444</html>