Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / content / test / data / media / peerconnection-call.html
index 043cbc2..511380d 100644 (file)
@@ -18,8 +18,8 @@
   // Default transform functions, overridden by some test cases.
   var transformSdp = function(sdp) { return sdp; };
   var transformRemoteSdp = function(sdp) { return sdp; };
-  var transformCandidate = function(candidate) { return candidate; };
-  var onLocalDescriptionError = function(error) { };
+  var onLocalDescriptionError = function(error) { failTest(error); };
+  var onRemoteDescriptionError = function(error) { failTest(error); };
 
   // Temporary measure to be able to force iSAC 16K where needed, particularly
   // on Android. This applies to every test which is why it's implemented like
   var maybeForceIsac16K = function(sdp) { return sdp; };
   function forceIsac16KInSdp() {
     maybeForceIsac16K = function(sdp) {
-      // Remove all other codecs (not the video codecs though). Also leave
-      // 126 for DTMF.
       sdp = sdp.replace(/m=audio (\d+) RTP\/SAVPF.*\r\n/g,
                         'm=audio $1 RTP/SAVPF 103 126\r\n');
       sdp = sdp.replace('a=fmtp:111 minptime=10', 'a=fmtp:103 minptime=10');
-      sdp = sdp.replace(
-          /a=rtpmap:(?!(103|126))\d{1,3} (?!X?VP8|red|ulpfec).*\r\n/g,
-          '');
+      if (sdp.search('a=rtpmap:103 ISAC/16000') == -1)
+        failTest('Missing iSAC 16K codec on Android; cannot force codec.');
       return sdp;
     };
+    sendValueToTest('isac-forced');
   }
 
   // When using external SDES, the crypto key is chosen by javascript.
         'inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj'
   };
 
-  // When using GICE, the ICE credentials can be chosen by javascript.
-  var EXTERNAL_GICE_UFRAG = '1234567890123456';
-  var EXTERNAL_GICE_PWD = '123456789012345678901234';
-
-  setAllEventsOccuredHandler(function() {
-    // The C++ tests look for this 'OK' in the title.
-    document.title = 'OK';
-  });
+  setAllEventsOccuredHandler(reportTestSuccess);
 
   // Test that we can setup call with an audio and video track.
   function call(constraints) {
     waitForVideo('remote-view-2');
   }
 
+  // Test that we can setup call with an audio and video track and check that
+  // the video resolution is as expected.
+  function callAndExpectResolution(constraints,
+                                   expected_width,
+                                   expected_height) {
+    createConnections(null);
+    navigator.webkitGetUserMedia(constraints,
+      addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
+    waitForVideoWithResolution('remote-view-1',
+                               expected_width,
+                               expected_height);
+    waitForVideoWithResolution('remote-view-2',
+                               expected_width,
+                               expected_height);
+  }
+
+
   // First calls without streams on any connections, and then adds a stream
   // to peer connection 1 which gets sent to peer connection 2. We must wait
   // for the first negotiation to complete before starting the second one, which
@@ -83,8 +91,9 @@
   }
 
   // First makes a call between pc1 and pc2, and then makes a call between pc3
-  // and pc4 where the remote streams from pc1 and pc2 will be used as the local
-  // streams of pc3 and pc4.
+  // and pc4. The stream sent from pc3 to pc4 is the stream received on pc1.
+  // The stream sent from pc4 to pc3 is cloned from the stream received on pc2
+  // to test that cloning of remote video tracks works as intended.
   function callAndForwardRemoteStream(constraints) {
     createConnections(null);
     navigator.webkitGetUserMedia(constraints,
       thirdConnection.addStream(gRemoteStreams['remote-view-1']);
 
       fourthConnection = createConnection(null, 'remote-view-4');
-      fourthConnection.addStream(gRemoteStreams['remote-view-2']);
+      fourthConnection.addStream(gRemoteStreams['remote-view-2'].clone());
 
       negotiateBetween(thirdConnection, fourthConnection);
 
     detectVideoPlaying('remote-view-2', onRemoteStream2);
   }
 
+  // First makes a call between pc1 and pc2, and then construct a new media
+  // stream using the remote audio and video tracks, connect the new media
+  // stream to a video element. These operations should not crash Chrome.
+  function ConnectChromiumSinkToRemoteAudioTrack() {
+    createConnections(null);
+    navigator.webkitGetUserMedia({audio: true, video: true},
+                                 addStreamToBothConnectionsAndNegotiate,
+                                 printGetUserMediaError);
+
+    detectVideoPlaying('remote-view-2', function() {
+      // Construct a new media stream with remote tracks.
+      var newStream = new webkitMediaStream();
+      newStream.addTrack(
+          gSecondConnection.getRemoteStreams()[0].getAudioTracks()[0]);
+      newStream.addTrack(
+          gSecondConnection.getRemoteStreams()[0].getVideoTracks()[0]);
+      var videoElement = document.createElement('video');
+
+      // No crash for this operation.
+      videoElement.src = URL.createObjectURL(newStream);
+      waitForVideo('remote-view-2');
+    });
+  }
+  
   // Test that we can setup call with an audio and video track and
   // simulate that the remote peer don't support MSID.
   function callWithoutMsidAndBundle() {
   function negotiateUnsupportedVideoCodec() {
     createConnections(null);
     transformSdp = removeVideoCodec;
+
     onLocalDescriptionError = function(error) {
       var expectedMsg = 'Failed to set local offer sdp:' +
           ' Session error code: ERROR_CONTENT. Session error description:' +
               ' Failed to set video receive codecs..';
-      expectEquals(expectedMsg, error);
-
-      // Got the right message, test succeeded.
-      document.title = 'OK';
+      assertEquals(expectedMsg, error);
+      reportTestSuccess();
     };
     navigator.webkitGetUserMedia({audio: true, video: true},
         addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
     transformSdp = removeCrypto;
     onLocalDescriptionError = function(error) {
       var expectedMsg = 'Failed to set local offer sdp:' +
-          ' Called with a SDP without crypto enabled.';
-      expectEquals(expectedMsg, error);
+          ' Called with SDP without DTLS fingerprint.';
 
-      // Got the right message, test succeeded.
-      document.title = 'OK';
+      assertEquals(expectedMsg, error);
+      reportTestSuccess();
     };
     navigator.webkitGetUserMedia({audio: true, video: true},
         addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
     createConnections(null);
     transformSdp = addBandwithControl;
     navigator.webkitGetUserMedia({audio: true, video: true},
-        addStreamToBothConnectionsAndNegotiate, printGetUserMediaError); 
+        addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
     waitForVideo('remote-view-1');
     waitForVideo('remote-view-2');
   }
     transformSdp = function(sdp) {
       return removeBundle(useGice(useExternalSdes(sdp)));
     };
-    transformCandidate = addGiceCredsToCandidate;
     createConnections({
       'mandatory': {'RtpDataChannels': true, 'DtlsSrtpKeyAgreement': false}
     });
     // Set an event handler for when the data channel has been closed.
     setAllEventsOccuredHandler(function() {
       // When the video is flowing the test is done.
-      setAllEventsOccuredHandler(function() {
-        document.title = 'OK';
-      });
+      setAllEventsOccuredHandler(reportTestSuccess);
       navigator.webkitGetUserMedia({audio: true, video: true},
         addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
       waitForVideo('remote-view-1');
       dtmfSender.ontonechange = onToneChange;
       dtmfSender.insertDTMF(tones);
       // Wait for the DTMF tones callback.
-      document.title = 'Waiting for dtmf...';
       addExpectedEvent();
       var waitDtmf = setInterval(function() {
         if (gSentTones == tones) {
     detectVideoPlaying('remote-view-2', onCallEstablished);
   }
 
+  function testCreateOfferOptions() {
+    createConnections(null);
+    var offerOptions = {
+      'offerToReceiveAudio': false,
+      'offerToReceiveVideo': true
+    };
+
+    gFirstConnection.createOffer(
+        function(offer) {
+          assertEquals(-1, offer.sdp.search('m=audio'));
+          assertNotEquals(-1, offer.sdp.search('m=video'));
+
+          reportTestSuccess();
+        },
+        function(error) { failTest(error); },
+        offerOptions);
+  }
+
   function enableRemoteVideo(peerConnection, enabled) {
     remoteStream = peerConnection.getRemoteStreams()[0];
     remoteVideoTrack = remoteStream.getVideoTracks()[0];
     remoteAudioTrack.enabled = enabled;
   }
 
-  function callAndEnsureAudioIsPlaying() {
+  function callAndEnsureAudioIsPlaying(beLenient, constraints) {
     createConnections(null);
-    navigator.webkitGetUserMedia({audio: true, video: true},
+    navigator.webkitGetUserMedia(constraints,
       addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
 
     // Wait until we have gathered samples and can conclude if audio is playing.
     addExpectedEvent();
     var onCallEstablished = function() {
-      gatherAudioLevelSamples(gSecondConnection, 300, 100,
-                              function(samples) {
-        verifyAudioIsPlaying(samples);
+      // Gather 50 samples per second for 2 seconds.
+      gatherAudioLevelSamples(gSecondConnection, 100, 50, function(samples) {
+        verifyAudioIsPlaying(samples, beLenient);
         eventOccured();
       });
-
-      // (Also, ensure video muting doesn't affect audio).
-      enableRemoteVideo(gSecondConnection, false);
     };
 
-    detectVideoPlaying('remote-view-2', onCallEstablished);
+    waitForConnectionToStabilize(gFirstConnection, onCallEstablished);
   }
 
-  function callAndEnsureAudioMutingWorks() {
-    callAndEnsureAudioIsPlaying();
+  function callAndEnsureAudioTrackMutingWorks(beLenient) {
+    callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
     setAllEventsOccuredHandler(function() {
       // Call is up, now mute the track and check everything goes silent (give
       // it a small delay though, we don't expect it to happen instantly).
       enableRemoteAudio(gSecondConnection, false);
 
       setTimeout(function() {
-        gatherAudioLevelSamples(gSecondConnection, 200, 100, function(samples) {
+        gatherAudioLevelSamples(gSecondConnection, 100, 50, function(samples) {
           verifyIsSilent(samples);
-          document.title = 'OK';
+          reportTestSuccess();
         });
       }, 500);
     });
   }
 
-  function callAndEnsureVideoMutingWorks() {
+  function callAndEnsureAudioTrackUnmutingWorks(beLenient) {
+    callAndEnsureAudioIsPlaying(beLenient, {audio: true, video: true});
+    setAllEventsOccuredHandler(function() {
+      // Mute, wait a while, unmute, verify audio gets back up.
+      // (Also, ensure video muting doesn't affect audio).
+      enableRemoteAudio(gSecondConnection, false);
+      enableRemoteVideo(gSecondConnection, false);
+
+      setTimeout(function() {
+        enableRemoteAudio(gSecondConnection, true);
+      }, 500);
+
+      setTimeout(function() {
+        // Sample for four seconds here; it can take a bit of time for audio to
+        // get back up after the unmute.
+        gatherAudioLevelSamples(gSecondConnection, 200, 50, function(samples) {
+          verifyAudioIsPlaying(samples, beLenient);
+          reportTestSuccess();
+        });
+      }, 1500);
+    });
+  }
+
+  function callAndEnsureVideoTrackMutingWorks() {
     createConnections(null);
     navigator.webkitGetUserMedia({audio: true, video: true},
       addStreamToBothConnectionsAndNegotiate, printGetUserMediaError);
       // received.
       addExpectedEvent();
       remote_stream_1.onaddtrack = function(){
-        expectEquals(remote_stream_1.getAudioTracks()[0].id,
+        assertEquals(remote_stream_1.getAudioTracks()[0].id,
                      local_stream.getAudioTracks()[0].id);
         eventOccured();
       }
       remote_stream_2 = gSecondConnection.getRemoteStreams()[0];
       addExpectedEvent();
       remote_stream_2.onaddtrack = function() {
-        expectEquals(remote_stream_2.getAudioTracks()[0].id,
+        assertEquals(remote_stream_2.getAudioTracks()[0].id,
                      local_stream.getAudioTracks()[0].id);
         eventOccured();
       }
         eventOccured();
       }
       // When all the above events have occurred- the test pass.
-      setAllEventsOccuredHandler(function() { document.title = 'OK'; });
+      setAllEventsOccuredHandler(reportTestSuccess);
 
       local_stream.addTrack(gLocalStream.getAudioTracks()[0]);
       local_stream.removeTrack(local_stream.getVideoTracks()[0]);
     var sendDataString = "send some text on a data channel."
     firstDataChannel = gFirstConnection.createDataChannel(
         "sendDataChannel", params);
-    expectEquals('connecting', firstDataChannel.readyState);
+    assertEquals('connecting', firstDataChannel.readyState);
 
     // When |firstDataChannel| transition to open state, send a text string.
     firstDataChannel.onopen = function() {
-      expectEquals('open', firstDataChannel.readyState);
-      firstDataChannel.send(sendDataString);
+      assertEquals('open', firstDataChannel.readyState);
+      if (firstDataChannel.reliable) {
+        firstDataChannel.send(sendDataString);
+      } else {
+        sendDataRepeatedlyUntilClosed(firstDataChannel);
+      }
     }
 
     // When |firstDataChannel| receive a message, close the channel and
     // initiate a new offer/answer exchange to complete the closure.
     firstDataChannel.onmessage = function(event) {
-      expectEquals(event.data, sendDataString);
+      assertEquals(event.data, sendDataString);
       firstDataChannel.close();
       negotiate();
     }
     // When |firstDataChannel| transition to closed state, the test pass.
     addExpectedEvent();
     firstDataChannel.onclose = function() {
-      expectEquals('closed', firstDataChannel.readyState);
+      assertEquals('closed', firstDataChannel.readyState);
       eventOccured();
     }
 
 
       // When |secondDataChannel| receive a message, send a message back.
       secondDataChannel.onmessage = function(event) {
-        expectEquals(event.data, sendDataString);
-        expectEquals('open', secondDataChannel.readyState);
-        secondDataChannel.send(sendDataString);
+        assertEquals(event.data, sendDataString);
+        console.log("gSecondConnection received data");
+        if (secondDataChannel.reliable) {
+          // If we're reliable we will just send one message over the channel,
+          // and therefore channel one's message handler cannot have shut us
+          // down already.
+          assertEquals('open', secondDataChannel.readyState);
+          secondDataChannel.send(sendDataString);
+        } else {
+          // If unreliable, this could be one in a series of messages and it
+          // is possible we already replied (which will close our channel).
+          sendDataRepeatedlyUntilClosed(secondDataChannel);
+        }
       }
     }
+
+    // Sends |sendDataString| on |dataChannel| every 200ms as long as
+    // |dataChannel| is open.
+    function sendDataRepeatedlyUntilClosed(dataChannel) {
+      var sendTimer = setInterval(function() {
+        if (dataChannel.readyState == 'open')
+          dataChannel.send(sendDataString);
+        else
+          clearInterval(sendTimer);
+      }, 200);
+    }
   }
 
   // SCTP data channel setup is slightly different then RTP based
     var sendDataString = "send some text on a data channel."
     firstDataChannel = gFirstConnection.createDataChannel(
         "sendDataChannel", params);
-    expectEquals('connecting', firstDataChannel.readyState);
+    assertEquals('connecting', firstDataChannel.readyState);
 
     // When |firstDataChannel| transition to open state, send a text string.
     firstDataChannel.onopen = function() {
-      expectEquals('open', firstDataChannel.readyState);
+      assertEquals('open', firstDataChannel.readyState);
     }
 
     // When |firstDataChannel| receive a message, send message back.
     // initiate a new offer/answer exchange to complete the closure.
     firstDataChannel.onmessage = function(event) {
-      expectEquals('open', firstDataChannel.readyState);
-      expectEquals(event.data, sendDataString);
+      assertEquals('open', firstDataChannel.readyState);
+      assertEquals(event.data, sendDataString);
       firstDataChannel.send(sendDataString);
     }
 
       // When |secondDataChannel| receive a message, close the channel and
       // initiate a new offer/answer exchange to complete the closure.
       secondDataChannel.onmessage = function(event) {
-        expectEquals(event.data, sendDataString);
-        expectEquals('open', secondDataChannel.readyState);
+        assertEquals(event.data, sendDataString);
+        assertEquals('open', secondDataChannel.readyState);
         secondDataChannel.close();
         negotiate();
       }
       // When |secondDataChannel| transition to closed state, the test pass.
       addExpectedEvent();
       secondDataChannel.onclose = function() {
-        expectEquals('closed', secondDataChannel.readyState);
+        assertEquals('closed', secondDataChannel.readyState);
         eventOccured();
       }
     }
 
   function onToneChange(tone) {
     gSentTones += tone.tone;
-    document.title = gSentTones;
   }
 
   function createConnections(constraints) {
     gFirstConnection = createConnection(constraints, 'remote-view-1');
-    expectEquals('stable', gFirstConnection.signalingState);
+    assertEquals('stable', gFirstConnection.signalingState);
 
     gSecondConnection = createConnection(constraints, 'remote-view-2');
-    expectEquals('stable', gSecondConnection.signalingState);
+    assertEquals('stable', gSecondConnection.signalingState);
   }
 
   function createConnection(constraints, remoteView) {
 
   // Called if getUserMedia fails.
   function printGetUserMediaError(error) {
-    document.title = 'getUserMedia request failed:';
+    var message = 'getUserMedia request unexpectedly failed:';
     if (error.constraintName)
-      document.title += ' could not satisfy constraint ' + error.constraintName;
+      message += ' could not satisfy constraint ' + error.constraintName;
     else
-      document.title += ' devices not working/user denied access.';
-    console.log(document.title);
+      message += ' devices not working/user denied access.';
+    failTest(message);
   }
 
   // Called if getUserMedia succeeds and we want to send from both connections.
   }
 
   function verifyHasOneAudioAndVideoTrack(stream) {
-    expectEquals(1, stream.getAudioTracks().length);
-    expectEquals(1, stream.getVideoTracks().length);
+    assertEquals(1, stream.getAudioTracks().length);
+    assertEquals(1, stream.getVideoTracks().length);
   }
 
   // Called if getUserMedia succeeds, then clone the stream, send two streams
     gFirstConnection.addStream(clonedStream);
 
     // Verify the local streams are correct.
-    expectEquals(2, gFirstConnection.getLocalStreams().length);
+    assertEquals(2, gFirstConnection.getLocalStreams().length);
     verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[0]);
     verifyHasOneAudioAndVideoTrack(gFirstConnection.getLocalStreams()[1]);
 
     }
     setAllEventsOccuredHandler(function() {
       // Negotiation complete, verify remote streams on the receiving side.
-      expectEquals(2, gSecondConnection.getRemoteStreams().length);
+      assertEquals(2, gSecondConnection.getRemoteStreams().length);
       verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[0]);
       verifyHasOneAudioAndVideoTrack(gSecondConnection.getRemoteStreams()[1]);
 
-      document.title = "OK";
+      reportTestSuccess();
     });
 
     negotiate();
     console.log("Negotiating call...");
     // Not stable = negotiation is ongoing. The behavior of re-negotiating while
     // a negotiation is ongoing is more or less undefined, so avoid this.
-    if (caller.signalingState != 'stable')
+    if (caller.signalingState != 'stable' || callee.signalingState != 'stable')
       throw 'You can only negotiate when the connection is stable!';
 
     connectOnIceCandidate(caller, callee);
   function onOfferCreated(offer, caller, callee) {
     offer.sdp = maybeForceIsac16K(transformSdp(offer.sdp));
     caller.setLocalDescription(offer, function() {
-      expectEquals('have-local-offer', caller.signalingState);
+      assertEquals('have-local-offer', caller.signalingState);
       receiveOffer(offer.sdp, caller, callee);
     }, onLocalDescriptionError);
   }
 
   function receiveOffer(offerSdp, caller, callee) {
-    console.log("Receiving offer...");
+    console.log("Receiving offer...\n" + offerSdp);
     offerSdp = transformRemoteSdp(offerSdp);
 
     var parsedOffer = new RTCSessionDescription({ type: 'offer',
                                                   sdp: offerSdp });
-    callee.setRemoteDescription(parsedOffer);
+    callee.setRemoteDescription(parsedOffer, function() {},
+                                onRemoteDescriptionError);
     callee.createAnswer(function (answer) {
                           onAnswerCreated(answer, caller, callee);
                         });
-    expectEquals('have-remote-offer', callee.signalingState);
+    assertEquals('have-remote-offer', callee.signalingState);
   }
 
   function removeMsid(offerSdp) {
                                 'b=AS:512\r\n');
     return offerSdp;
   }
-  
+
   function removeBundle(sdp) {
     return sdp.replace(/a=group:BUNDLE .*\r\n/g, '');
   }
     sdp = sdp.replace(/t=.*\r\n/g, function(subString) {
       return subString + 'a=ice-options:google-ice\r\n';
     });
-    sdp = sdp.replace(/a=ice-ufrag:.*\r\n/g,
-                      'a=ice-ufrag:' + EXTERNAL_GICE_UFRAG + '\r\n');
-    sdp = sdp.replace(/a=ice-pwd:.*\r\n/g,
-                      'a=ice-pwd:' + EXTERNAL_GICE_PWD + '\r\n');
     return sdp;
   }
 
 
   function onAnswerCreated(answer, caller, callee) {
     answer.sdp = maybeForceIsac16K(transformSdp(answer.sdp));
-    callee.setLocalDescription(answer);
-    expectEquals('stable', callee.signalingState);
+    callee.setLocalDescription(answer,
+                               function () {
+                                 assertEquals('stable', callee.signalingState);
+                               },
+                               onLocalDescriptionError);
     receiveAnswer(answer.sdp, caller);
   }
 
     answerSdp = transformRemoteSdp(answerSdp);
     var parsedAnswer = new RTCSessionDescription({ type: 'answer',
                                                    sdp: answerSdp });
-    caller.setRemoteDescription(parsedAnswer);
-    expectEquals('stable', caller.signalingState);
+    caller.setRemoteDescription(parsedAnswer,
+                                function() {
+                                  assertEquals('stable', caller.signalingState);
+                                },
+                                onRemoteDescriptionError);
   }
 
   function connectOnIceCandidate(caller, callee) {
     callee.onicecandidate = function(event) { onIceCandidate(event, caller); }
   }
 
-  function addGiceCredsToCandidate(candidate) {
-    return candidate.trimRight() +
-        ' username ' + EXTERNAL_GICE_UFRAG + ' password ' + EXTERNAL_GICE_PWD;
-  }
-
   function onIceCandidate(event, target) {
     if (event.candidate) {
       var candidate = new RTCIceCandidate(event.candidate);
-      candidate.candidate = transformCandidate(candidate.candidate);
       target.addIceCandidate(candidate);
     }
   }
   function onRemoteStream(e, target) {
     console.log("Receiving remote stream...");
     if (gTestWithoutMsid && e.stream.id != "default") {
-      document.title = 'a default remote stream was expected but instead ' +
-          e.stream.id + ' was received.';
-      return;
+      failTest('a default remote stream was expected but instead ' +
+          e.stream.id + ' was received.');
     }
     gRemoteStreams[target] = e.stream;
     var remoteStreamUrl = URL.createObjectURL(e.stream);