chromium/content/test/data/media/peerconnection-call-data.html


<html>
<head>
  <script type="text/javascript" src="webrtc_test_utilities.js"></script>
  <script type="text/javascript" src="webrtc_test_common.js"></script>
  <script type="text/javascript">
  $ = function(id) {
    return document.getElementById(id);
  };

  var gFirstConnection = null;
  var gSecondConnection = null;
  var gLocalStream = null;

  var gRemoteStreams = {};

  function callWithSctpDataOnly() {
    createConnections();
    return Promise.all([
      promiseSctpDataChannelExchange({reliable: true}),
      negotiate(),
    ])
    .then(logSuccess);
  }

  function callWithSctpDataAndMedia() {
    createConnections();
    var hasExchanged = promiseSctpDataChannelExchange({reliable: true});
    return Promise.all([
      navigator.mediaDevices.getUserMedia({audio: true, video: true})
          .then(addStreamToBothConnectionsAndNegotiate),
      hasExchanged,
      detectVideoPlaying('remote-view-1'),
      detectVideoPlaying('remote-view-2')
    ])
    .then(logSuccess);
  }

  // This function is used for setting up a test that:
  // 1. Creates a data channel on |gFirstConnection| and sends data to
  //    |gSecondConnection|.
  // 2. When data is received on |gSecondConnection| a message
  //    is sent to |gFirstConnection|.
  // 3. When data is received on |gFirstConnection|, the data
  //    channel is closed. This function returns a promise that resolves when
  //    that last channel is closed.
  //
  // Note: you need to negotiate after calling this function, or the exchange
  // will not happen, and the promise will not resolve.
  function promiseDataChannelExchange(params) {
    var sendDataString = "send some text on a data channel."
    firstDataChannel = gFirstConnection.createDataChannel(
        "sendDataChannel", params);
    assertEquals('connecting', firstDataChannel.readyState);

    // When |firstDataChannel| transition to open state, send a text string.
    firstDataChannel.onopen = function() {
      assertEquals('open', firstDataChannel.readyState);
      firstDataChannel.send(sendDataString);
    }

    // When |firstDataChannel| receive a message, close the channel and
    // initiate a new offer/answer exchange to complete the closure.
    firstDataChannel.onmessage = function(event) {
      assertEquals(event.data, sendDataString);
      firstDataChannel.close();
      negotiate();
    }

    // When |firstDataChannel| transition to closed state, the test pass.
    var closedPromise = new Promise((resolve, reject) => {
      firstDataChannel.onclose = function() {
        assertEquals('closed', firstDataChannel.readyState);
        resolve();
      }
    });

    // Event handler for when |gSecondConnection| receive a new dataChannel.
    gSecondConnection.ondatachannel = function (event) {
      // Make secondDataChannel global to make sure it's not gc'd.
      secondDataChannel = event.channel;

      // When |secondDataChannel| receive a message, send a message back.
      secondDataChannel.onmessage = function(event) {
        assertEquals(event.data, sendDataString);
        console.log("gSecondConnection received data");
        assertEquals('open', secondDataChannel.readyState);
        secondDataChannel.send(sendDataString);
      }
    }

    return closedPromise;
  }

  // SCTP data channel setup is slightly different then RTP based
  // channels. Due to a bug in libjingle, we can't send data immediately
  // after channel becomes open. So for that reason in SCTP,
  // we are sending data from second channel, when ondatachannel event is
  // received. So data flow happens 2 -> 1 -> 2.
  // Note: you need to negotiate after calling this function, or the exchange
  // will not happen, and the promise will not resolve.
  function promiseSctpDataChannelExchange(params) {
    var sendDataString = "send some text on a data channel."
    firstDataChannel = gFirstConnection.createDataChannel(
        "sendDataChannel", params);
    assertEquals('connecting', firstDataChannel.readyState);

    // When |firstDataChannel| transition to open state, send a text string.
    firstDataChannel.onopen = function() {
      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) {
      assertEquals('open', firstDataChannel.readyState);
      assertEquals(event.data, sendDataString);
      firstDataChannel.send(sendDataString);
    }

    return new Promise((resolve, reject) => {
      // Event handler for when |gSecondConnection| receive a new dataChannel.
      gSecondConnection.ondatachannel = function (event) {
        // Make secondDataChannel global to make sure it's not gc'd.
        secondDataChannel = event.channel;
        secondDataChannel.onopen = function() {
          secondDataChannel.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) {
          assertEquals(event.data, sendDataString);
          assertEquals('open', secondDataChannel.readyState);
          secondDataChannel.close();
          negotiate();
        }

        // When |secondDataChannel| transition to closed state, we're done.
        secondDataChannel.onclose = function() {
          assertEquals('closed', secondDataChannel.readyState);
          resolve();
        }
      }
    });
  }

  function addStreamToBothConnectionsAndNegotiate(localStream) {
    displayAndRemember(localStream);
    gFirstConnection.addStream(localStream);
    gSecondConnection.addStream(localStream);
    negotiate();
  }

  function createConnections() {
    gFirstConnection = createConnection('remote-view-1');
    assertEquals('stable', gFirstConnection.signalingState);

    gSecondConnection = createConnection('remote-view-2');
    assertEquals('stable', gSecondConnection.signalingState);
  }

  function createConnection(remoteView) {
    var pc = new RTCPeerConnection();
    pc.onaddstream = function(event) {
      onRemoteStream(event, remoteView);
    }
    return pc;
  }

  function displayAndRemember(localStream) {
    $('local-view').srcObject = localStream;

    gLocalStream = localStream;
  }

  function negotiate() {
    Promise.all([
      waitForConnectionToStabilizeIfNeeded(gFirstConnection),
      waitForConnectionToStabilizeIfNeeded(gSecondConnection),
    ]).then(() => {
      negotiateBetween(gFirstConnection, gSecondConnection);
    });
  }

  function onRemoteStream(e, target) {
    console.log("Receiving remote stream...");
    gRemoteStreams[target] = e.stream;
    var remoteVideo = $(target);
    remoteVideo.srcObject = e.stream;
  }

  function connectOnIceCandidate(caller, callee) {
    caller.onicecandidate = function(event) { onIceCandidate(event, callee); }
    callee.onicecandidate = function(event) { onIceCandidate(event, caller); }
  }

  function onIceCandidate(event, target) {
    if (event.candidate) {
      var candidate = new RTCIceCandidate(event.candidate);
      target.addIceCandidate(candidate);
    }
  }

  function removeBundle(sdp) {
    return sdp.replace(/a=group:BUNDLE .*\r\n/g, '');
  }

  </script>
</head>
<body>
  <table border="0">
    <tr>
      <td><video width="320" height="240" id="local-view" style="display:none"
          autoplay muted></video></td>
      <td><video width="320" height="240" id="remote-view-1"
          style="display:none" autoplay></video></td>
      <td><video width="320" height="240" id="remote-view-2"
          style="display:none" autoplay></video></td>
      <!-- Canvases are named after their corresponding video elements. -->
      <td><canvas width="320" height="240" id="remote-view-1-canvas"
          style="display:none"></canvas></td>
      <td><canvas width="320" height="240" id="remote-view-2-canvas"
          style="display:none"></canvas></td>
    </tr>
  </table>
</body>
</html>