<!DOCTYPE HTML>
<html>
<head>
  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
  title: "getUserMedia in multiple iframes with different constraints",
  bug: "1404977"
});
/**
  * Verify that we can successfully call getUserMedia for the same device in
  * multiple iframes concurrently. This is checked by creating a number of
  * iframes and performing a separate getUserMedia call in each. We verify the
  * stream returned by that call has the same constraints as requested both
  * immediately after the call and after all gUM calls have been made. The test
  * then verifies the streams can be played.
  */
runTest(async function() {
  // Compare constraints and return a string with the differences in
  // echoCancellation, autoGainControl, and noiseSuppression. The string
  // will be empty if there are no differences.
  function getConstraintDifferenceString(constraints, otherConstraints) {
    let diffString = "";
    if (constraints.echoCancellation != otherConstraints.echoCancellation) {
      diffString += "echoCancellation different: " +
                    `${constraints.echoCancellation} != ${otherConstraints.echoCancellation}, `;
    }
    if (constraints.autoGainControl != otherConstraints.autoGainControl) {
      diffString += "autoGainControl different: " +
                    `${constraints.autoGainControl} != ${otherConstraints.autoGainControl}, `;
    }
    if (constraints.noiseSuppression != otherConstraints.noiseSuppression) {
      diffString += "noiseSuppression different: " +
                    `${constraints.noiseSuppression} != ${otherConstraints.noiseSuppression}, `;
    }
    // Replace trailing comma and space if any
    return diffString.replace(/, $/, "");
  }

  // We need a real device to get a MediaEngine supporting constraints
  let audioDevice = SpecialPowers.getCharPref("media.audio_loopback_dev", "");
  if (!audioDevice) {
    todo(false, "No device set by framework. Try --use-test-media-devices");
    return;
  }

  let egn = (e, g, n) => ({
    echoCancellation: e,
    autoGainControl: g,
    noiseSuppression: n
  });

  let allConstraintCombinations = [
    egn(false, false, false),
    egn(true,  false, false),
    egn(false, true,  false),
    egn(false, false, true),
    egn(true,  true,  false),
    egn(true,  false, true),
    egn(false, true,  true),
    egn(true,  true,  true),
  ];

  // TODO: We would like to be able to perform an arbitrary number of gUM calls
  // at once, but issues with pulse and audio IPC mean on some systems we're
  // limited to as few as 2 concurrent calls. To avoid issues we chunk test runs
  // to only two calls at a time. The while, splice and GC lines can be removed,
  // the extra scope removed and allConstraintCombinations can be renamed to
  // constraintCombinations once this issue is resolved. See bug 1480489.
  while (allConstraintCombinations.length) {
    {
      let constraintCombinations = allConstraintCombinations.splice(0, 2);
      // Array to store objects that associate information used in our test such as
      // constraints, iframes, gum streams, and various promises.
      let testCases = [];

      for (let constraints of constraintCombinations) {
        let testCase = {requestedConstraints: constraints};
        // Provide an id for logging, labeling related elements.
        testCase.id = `testCase.` +
                      `e=${constraints.echoCancellation}.` +
                      `g=${constraints.noiseSuppression}.` +
                      `n=${constraints.noiseSuppression}`;
        testCases.push(testCase);
        testCase.iframe = document.createElement("iframe");
        testCase.iframeLoadedPromise = new Promise((resolve, reject) => {
          testCase.iframe.onload = () => { resolve(); };
        });
        document.body.appendChild(testCase.iframe);
      }
      is(testCases.length,
        constraintCombinations.length,
        "Should have created a testcase for each constraint");

      // Wait for all iframes to be loaded
      await Promise.all(testCases.map(tc => tc.iframeLoadedPromise));

      // Start a tone at our top level page so the gUM calls will record something
      // should we wish to verify their recording in future.
      let tone = new LoopbackTone(new AudioContext, TEST_AUDIO_FREQ);
      tone.start();

      // One by one see if we can grab a gUM stream per iframe
      for (let testCase of testCases) {
        // Use normal gUM rather than our test helper as the test harness was
        // not made to be used inside iframes.
        testCase.gumStream =
          await testCase.iframe.contentWindow.navigator.mediaDevices.getUserMedia({audio: testCase.requestedConstraints})
          .catch(e => Promise.reject(`getUserMedia calls should not fail! Failed at ${testCase.id} with: ${e}!`));
        let differenceString = getConstraintDifferenceString(
          testCase.requestedConstraints,
          testCase.gumStream.getAudioTracks()[0].getSettings());
        ok(!differenceString,
          `gUM stream for ${testCase.id} should have the same constraints as were ` +
          `requested from gUM. Differences: ${differenceString}`);
      }

      // Once all streams are collected, make sure the constraints haven't been
      // mutated by another gUM call.
      for (let testCase of testCases) {
        let differenceString = getConstraintDifferenceString(
          testCase.requestedConstraints,
          testCase.gumStream.getAudioTracks()[0].getSettings());
        ok(!differenceString,
          `gUM stream for ${testCase.id} should not have had constraints altered after ` +
          `all gUM calls are done. Differences: ${differenceString}`);
      }

      // We do not currently have tests to verify the behaviour of the different
      // constraints. Once we do we should do further verification here. See
      // bug 1406372, bug 1406376, and bug 1406377.

      for (let testCase of testCases) {
        let testAudio = createMediaElement("audio", `testAudio.${testCase.id}`);
        let playback = new MediaStreamPlayback(testAudio, testCase.gumStream);
        await playback.playMediaWithoutStoppingTracks(false);
      }

      // Stop the tracks for each stream, we left them running above via
      // playMediaWithoutStoppingTracks to make sure they can play concurrently.
      for (let testCase of testCases) {
        testCase.gumStream.getTracks().map(t => t.stop());
        document.body.removeChild(testCase.iframe);
      }

      tone.stop();
    }
    await new Promise(r => SpecialPowers.exactGC(r));
  }
});
</script>
</pre>
</body>
</html>
