Home | History | Annotate | Download | only in media
      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