<!DOCTYPE HTML>
<html>
<head>
  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
"use strict";

createHTML({
  title: "MediaStream.clone()",
  bug: "1208371"
});

runTest(async () => {
  await pushPrefs(
    ["media.getusermedia.camera.stop_on_disable.enabled", true],
    ["media.getusermedia.camera.stop_on_disable.delay_ms", 0],
    ["media.getusermedia.microphone.stop_on_disable.enabled", true],
    ["media.getusermedia.microphone.stop_on_disable.delay_ms", 0]);

  let gUMStream = await getUserMedia({audio: true, video: true});
  {
    info("Test clone()ing an audio/video gUM stream");
    let clone = gUMStream.clone();

    checkMediaStreamCloneAgainstOriginal(clone, gUMStream);
    checkMediaStreamTrackCloneAgainstOriginal(clone.getAudioTracks()[0],
                                              gUMStream.getAudioTracks()[0]);
    checkMediaStreamTrackCloneAgainstOriginal(clone.getVideoTracks()[0],
                                              gUMStream.getVideoTracks()[0]);

    isnot(clone.id.length, 0, "Stream clone should have an id string");
    isnot(clone.getAudioTracks()[0].id.length, 0,
          "Audio track clone should have an id string");
    isnot(clone.getVideoTracks()[0].id.length, 0,
          "Audio track clone should have an id string");

    info("Playing from track clones");
    let test = createMediaElement('video', 'testClonePlayback');
    let playback = new MediaStreamPlayback(test, clone);
    await playback.playMedia(false);
  }

  {
    info("Test addTrack()ing a video track to a stream without affecting its clone");
    let stream = new MediaStream(gUMStream.getVideoTracks());
    let otherStream = await getUserMedia({video: true});
    let track = stream.getTracks()[0];
    let otherTrack = otherStream.getTracks()[0];

    let streamClone = stream.clone();
    let trackClone = streamClone.getTracks()[0];
    checkMediaStreamContains(streamClone, [trackClone], "Initial clone");

    stream.addTrack(otherTrack);
    checkMediaStreamContains(stream, [track, otherTrack],
                             "Added video to original");
    checkMediaStreamContains(streamClone, [trackClone],
                             "Clone not affected");

    stream.removeTrack(track);
    streamClone.addTrack(track);
    checkMediaStreamContains(streamClone, [trackClone, track],
                             "Added video to clone");
    checkMediaStreamContains(stream, [otherTrack],
                             "Original not affected");

    // Not part of streamClone. Does not get stopped by the playback test.
    otherTrack.stop();

    let test = createMediaElement('video', 'testClonePlayback');
    let playback = new MediaStreamPlayback(test, streamClone);
    await playback.playMedia(false);
  }

  {
    info("Test cloning a stream into inception");
    let stream = gUMStream.clone()
    let clone = stream;
    let clones = Array(10).fill().map(() => clone = clone.clone());
    let inceptionClone = clones.pop();
    checkMediaStreamCloneAgainstOriginal(inceptionClone, stream);
    stream.getTracks().forEach(t => (stream.removeTrack(t),
                                     inceptionClone.addTrack(t)));
    is(inceptionClone.getAudioTracks().length, 2,
       "The inception clone should contain the original audio track and a track clone");
    is(inceptionClone.getVideoTracks().length, 2,
       "The inception clone should contain the original video track and a track clone");

    let test = createMediaElement('video', 'testClonePlayback');
    let playback = new MediaStreamPlayback(test, inceptionClone);
    await playback.playMedia(false);
    clones.forEach(c => c.getTracks().forEach(t => t.stop()));
    stream.getTracks().forEach(t => t.stop());
  }

  {
    info("Test adding tracks from many stream clones to the original stream");
    let stream = gUMStream.clone();

    const LOOPS = 3;
    for (let i = 0; i < LOOPS; i++) {
      stream.clone().getTracks().forEach(t => stream.addTrack(t));
    }
    is(stream.getAudioTracks().length, Math.pow(2, LOOPS),
       "The original track should contain the original audio track and all the audio clones");
    is(stream.getVideoTracks().length, Math.pow(2, LOOPS),
       "The original track should contain the original video track and all the video clones");
    stream.getTracks().forEach(t1 => is(stream.getTracks()
                                              .filter(t2 => t1.id == t2.id)
                                              .length,
                                        1, "Each track should be unique"));

    let test = createMediaElement('video', 'testClonePlayback');
    let playback = new MediaStreamPlayback(test, stream);
    await playback.playMedia(false);
  }

  {
    info("Testing audio content routing with MediaStream.clone()");
    let ac = new AudioContext();

    let osc1kOriginal = createOscillatorStream(ac, 1000);
    let audioTrack1kOriginal = osc1kOriginal.getTracks()[0];
    let audioTrack1kClone = osc1kOriginal.clone().getTracks()[0];

    let osc5kOriginal = createOscillatorStream(ac, 5000);
    let audioTrack5kOriginal = osc5kOriginal.getTracks()[0];
    let audioTrack5kClone = osc5kOriginal.clone().getTracks()[0];

    info("Analysing audio output of original stream (1k + 5k)");
    let stream = new MediaStream();
    stream.addTrack(audioTrack1kOriginal);
    stream.addTrack(audioTrack5kOriginal);

    let analyser = new AudioStreamAnalyser(ac, stream);
    await analyser.waitForAnalysisSuccess(array =>
            array[analyser.binIndexForFrequency(50)] < 50 &&
            array[analyser.binIndexForFrequency(1000)] > 200 &&
            array[analyser.binIndexForFrequency(3000)] < 50 &&
            array[analyser.binIndexForFrequency(5000)] > 200 &&
            array[analyser.binIndexForFrequency(10000)] < 50);

    info("Waiting for original tracks to stop");
    stream.getTracks().forEach(t => t.stop());
    await analyser.waitForAnalysisSuccess(array =>
            array[analyser.binIndexForFrequency(50)] < 50 &&
            // WebAudioDestination streams do not handle stop()
            // XXX Should they? Plan to resolve that in bug 1208384.
            // array[analyser.binIndexForFrequency(1000)] < 50 &&
            array[analyser.binIndexForFrequency(3000)] < 50 &&
            // array[analyser.binIndexForFrequency(5000)] < 50 &&
            array[analyser.binIndexForFrequency(10000)] < 50);
    analyser.disconnect();

    info("Analysing audio output of stream clone (1k + 5k)");
    stream = new MediaStream();
    stream.addTrack(audioTrack1kClone);
    stream.addTrack(audioTrack5kClone);

    analyser = new AudioStreamAnalyser(ac, stream);
    await analyser.waitForAnalysisSuccess(array =>
            array[analyser.binIndexForFrequency(50)] < 50 &&
            array[analyser.binIndexForFrequency(1000)] > 200 &&
            array[analyser.binIndexForFrequency(3000)] < 50 &&
            array[analyser.binIndexForFrequency(5000)] > 200 &&
            array[analyser.binIndexForFrequency(10000)] < 50);
    analyser.disconnect();

    info("Analysing audio output of clone of clone (1k + 5k)");
    stream = new MediaStream([audioTrack1kClone, audioTrack5kClone]).clone();

    analyser = new AudioStreamAnalyser(ac, stream);
    await analyser.waitForAnalysisSuccess(array =>
            array[analyser.binIndexForFrequency(50)] < 50 &&
            array[analyser.binIndexForFrequency(1000)] > 200 &&
            array[analyser.binIndexForFrequency(3000)] < 50 &&
            array[analyser.binIndexForFrequency(5000)] > 200 &&
            array[analyser.binIndexForFrequency(10000)] < 50);
    analyser.disconnect();

    info("Analysing audio output of clone() + addTrack()ed tracks (1k + 5k)");
    stream = new MediaStream(new MediaStream([ audioTrack1kClone
                                             , audioTrack5kClone
                                             ]).clone().getTracks());

    analyser = new AudioStreamAnalyser(ac, stream);
    await analyser.waitForAnalysisSuccess(array =>
            array[analyser.binIndexForFrequency(50)] < 50 &&
            array[analyser.binIndexForFrequency(1000)] > 200 &&
            array[analyser.binIndexForFrequency(3000)] < 50 &&
            array[analyser.binIndexForFrequency(5000)] > 200 &&
            array[analyser.binIndexForFrequency(10000)] < 50);
    analyser.disconnect();

    info("Analysing audio output of clone()d tracks in original stream (1k) " +
         "and clone()d tracks in stream clone (5k)");
    stream = new MediaStream([audioTrack1kClone, audioTrack5kClone]);
    let streamClone = stream.clone();

    stream.getTracks().forEach(t => stream.removeTrack(t));
    stream.addTrack(streamClone.getTracks()[0]);
    streamClone.removeTrack(streamClone.getTracks()[0]);

    analyser = new AudioStreamAnalyser(ac, stream);
    await analyser.waitForAnalysisSuccess(array =>
            array[analyser.binIndexForFrequency(50)] < 50 &&
            array[analyser.binIndexForFrequency(1000)] > 200 &&
            array[analyser.binIndexForFrequency(3000)] < 50 &&
            array[analyser.binIndexForFrequency(5000)] < 50);
    analyser.disconnect();

    let cloneAnalyser = new AudioStreamAnalyser(ac, streamClone);
    await cloneAnalyser.waitForAnalysisSuccess(array =>
            array[cloneAnalyser.binIndexForFrequency(1000)] < 50 &&
            array[cloneAnalyser.binIndexForFrequency(3000)] < 50 &&
            array[cloneAnalyser.binIndexForFrequency(5000)] > 200 &&
            array[cloneAnalyser.binIndexForFrequency(10000)] < 50);
    cloneAnalyser.disconnect();

    info("Analysing audio output enabled and disabled tracks that don't affect each other");
    stream = new MediaStream([audioTrack1kClone, audioTrack5kClone]);
    let clone = stream.clone();

    stream.getTracks()[0].enabled = true;
    stream.getTracks()[1].enabled = false;

    clone.getTracks()[0].enabled = false;
    clone.getTracks()[1].enabled = true;

    analyser = new AudioStreamAnalyser(ac, stream);
    await analyser.waitForAnalysisSuccess(array =>
            array[analyser.binIndexForFrequency(50)] < 50 &&
            array[analyser.binIndexForFrequency(1000)] > 200 &&
            array[analyser.binIndexForFrequency(3000)] < 50 &&
            array[analyser.binIndexForFrequency(5000)] < 50);
    analyser.disconnect();

    cloneAnalyser = new AudioStreamAnalyser(ac, clone);
    await cloneAnalyser.waitForAnalysisSuccess(array =>
            array[cloneAnalyser.binIndexForFrequency(1000)] < 50 &&
            array[cloneAnalyser.binIndexForFrequency(3000)] < 50 &&
            array[cloneAnalyser.binIndexForFrequency(5000)] > 200 &&
            array[cloneAnalyser.binIndexForFrequency(10000)] < 50);
    cloneAnalyser.disconnect();

    // Restore original tracks
    stream.getTracks().forEach(t => t.enabled = true);
  }

  gUMStream.getTracks().forEach(t => t.stop());
});
</script>
</pre>
</body>
</html>
