Bug 1208371 - Test DOMMediaStream::Clone(). r?jib draft
authorAndreas Pehrson <pehrsons@gmail.com>
Tue, 05 Jan 2016 10:16:26 +0800
changeset 342126 38a71f9332fc0706613d3c76bdd333b2be52752f
parent 342125 49e0c86c7bf774c992559fa9328c6ee384875734
child 342127 2c269d1c2c1306ff9f06cc7e5a833ef8e05c85f5
push id13352
push userpehrsons@gmail.com
push dateFri, 18 Mar 2016 13:49:47 +0000
reviewersjib
bugs1208371
milestone47.0a1
Bug 1208371 - Test DOMMediaStream::Clone(). r?jib MozReview-Commit-ID: Cp5Y8ayjMH6
dom/media/tests/mochitest/head.js
dom/media/tests/mochitest/mochitest.ini
dom/media/tests/mochitest/test_getUserMedia_mediaStreamClone.html
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -366,16 +366,44 @@ function checkMediaStreamContains(mediaS
   message = message ? (message + ": ") : "";
   tracks.forEach(t => ok(mediaStream.getTracks().includes(t),
                          message + "MediaStream " + mediaStream.id +
                          " contains track " + t.id));
   is(mediaStream.getTracks().length, tracks.length,
      message + "MediaStream " + mediaStream.id + " contains no extra tracks");
 }
 
+function checkMediaStreamCloneAgainstOriginal(clone, original) {
+  isnot(clone.id.length, 0, "Stream clone should have an id string");
+  isnot(clone, original,
+        "Stream clone should be different from the original");
+  isnot(clone.id, original.id,
+        "Stream clone's id should be different from the original's");
+  is(clone.getAudioTracks().length, original.getAudioTracks().length,
+     "All audio tracks should get cloned");
+  is(clone.getVideoTracks().length, original.getVideoTracks().length,
+     "All video tracks should get cloned");
+  original.getTracks()
+          .forEach(t => ok(!clone.getTracks().includes(t),
+                           "The clone's tracks should be originals"));
+}
+
+function checkMediaStreamTrackCloneAgainstOriginal(clone, original) {
+  isnot(clone.id.length, 0,
+        "Track clone should have an id string");
+  isnot(clone, original,
+        "Track clone should be different from the original");
+  isnot(clone.id, original.id,
+        "Track clone's id should be different from the original's");
+  is(clone.kind, original.kind,
+     "Track clone's kind should be same as the original's");
+  is(clone.enabled, original.enabled,
+     "Track clone's kind should be same as the original's");
+}
+
 /*** Utility methods */
 
 /** The dreadful setTimeout, use sparingly */
 function wait(time) {
   return new Promise(r => setTimeout(r, time));
 }
 
 /** The even more dreadful setInterval, use even more sparingly */
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -50,16 +50,17 @@ skip-if = buildapp == 'b2g' || toolkit =
 [test_getUserMedia_basicVideoAudio.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure, turned an intermittent (bug 962579) into a permanant orange
 [test_getUserMedia_bug1223696.html]
 [test_getUserMedia_constraints.html]
 [test_getUserMedia_callbacks.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # Bug 1063290, intermittent timeout # TC: Bug 1144079 - Re-enable Mulet mochitests and reftests taskcluster-specific disables.
 [test_getUserMedia_gumWithinGum.html]
 [test_getUserMedia_loadedmetadata.html]
+[test_getUserMedia_mediaStreamClone.html]
 [test_getUserMedia_mediaStreamConstructors.html]
 [test_getUserMedia_playAudioTwice.html]
 [test_getUserMedia_playVideoAudioTwice.html]
 [test_getUserMedia_playVideoTwice.html]
 [test_getUserMedia_spinEventLoop.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # copied from basicAudio
 [test_getUserMedia_stopAudioStream.html]
 [test_getUserMedia_stopAudioStreamWithFollowupAudio.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_getUserMedia_mediaStreamClone.html
@@ -0,0 +1,241 @@
+<!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(() => Promise.resolve()
+    .then(() => getUserMedia({audio: true, video: true})).then(stream => {
+      info("Test clone()ing an audio/video gUM stream");
+      var clone = stream.clone();
+
+      checkMediaStreamCloneAgainstOriginal(clone, stream);
+      checkMediaStreamTrackCloneAgainstOriginal(clone.getAudioTracks()[0],
+                                                stream.getAudioTracks()[0]);
+      checkMediaStreamTrackCloneAgainstOriginal(clone.getVideoTracks()[0],
+                                                stream.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("Stopping original tracks");
+      stream.getTracks().forEach(t => t.stop());
+
+      info("Playing from track clones");
+      var test = createMediaElement('video', 'testClonePlayback');
+      var playback = new MediaStreamPlayback(test, clone);
+      return playback.playMediaWithMediaStreamTracksStop(false);
+    })
+    .then(() => getUserMedia({video: true})).then(stream =>
+      getUserMedia({video: true}).then(otherStream => {
+        info("Test addTrack()ing a video track to a stream without affecting its clone");
+        var track = stream.getTracks()[0];
+        var otherTrack = otherStream.getTracks()[0];
+
+        var streamClone = stream.clone();
+        var 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();
+        otherStream.stop();
+
+        var test = createMediaElement('video', 'testClonePlayback');
+        var playback = new MediaStreamPlayback(test, streamClone);
+        return playback.playMediaWithMediaStreamTracksStop(false)
+          .then(() => stream.getTracks().forEach(t => t.stop()))
+          .then(() => stream.stop());
+    }))
+    .then(() => getUserMedia({audio: true, video: true})).then(stream => {
+      info("Test cloning a stream into inception");
+      var inceptionClone = stream.clone().clone().clone().clone().clone()
+                                 .clone().clone().clone().clone().clone();
+      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");
+
+      var test = createMediaElement('video', 'testClonePlayback');
+      var playback = new MediaStreamPlayback(test, inceptionClone);
+      return playback.playMediaWithMediaStreamTracksStop(false);
+    })
+    .then(() => getUserMedia({audio: true, video: true})).then(stream => {
+      info("Test adding tracks from many stream clones to the original stream");
+
+      const LOOPS = 3;
+      for (var 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"));
+
+      var test = createMediaElement('video', 'testClonePlayback');
+      var playback = new MediaStreamPlayback(test, stream);
+      return playback.playMediaWithMediaStreamTracksStop(false);
+    })
+    .then(() => {
+      info("Testing audio content routing with MediaStream.clone()");
+      var ac = new AudioContext();
+
+      var osc1kOriginal = createOscillatorStream(ac, 1000);
+      var audioTrack1kOriginal = osc1kOriginal.getTracks()[0];
+      var audioTrack1kClone = osc1kOriginal.clone().getTracks()[0];
+
+      var osc5kOriginal = createOscillatorStream(ac, 5000);
+      var audioTrack5kOriginal = osc5kOriginal.getTracks()[0];
+      var audioTrack5kClone = osc5kOriginal.clone().getTracks()[0];
+
+      return Promise.resolve().then(() => {
+        info("Analysing audio output of original stream (1k + 5k)");
+        var stream = new MediaStream();
+        stream.addTrack(audioTrack1kOriginal);
+        stream.addTrack(audioTrack5kOriginal);
+
+        var analyser = new AudioStreamAnalyser(ac, stream);
+        return 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)
+          .then(() => {
+            info("Waiting for original tracks to stop");
+            stream.getTracks().forEach(t => t.stop());
+            return 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);
+          })
+      }).then(() => {
+        info("Analysing audio output of stream clone (1k + 5k)");
+        var stream = new MediaStream();
+        stream.addTrack(audioTrack1kClone);
+        stream.addTrack(audioTrack5kClone);
+
+        var analyser = new AudioStreamAnalyser(ac, stream);
+        return 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);
+      }).then(() => {
+        info("Analysing audio output of clone of clone (1k + 5k)");
+        var stream = new MediaStream([audioTrack1kClone, audioTrack5kClone]).clone();
+
+        var analyser = new AudioStreamAnalyser(ac, stream);
+        return 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);
+      }).then(() => {
+        info("Analysing audio output of clone() + addTrack()ed tracks (1k + 5k)");
+        var stream =
+          new MediaStream(new MediaStream([ audioTrack1kClone
+                                          , audioTrack5kClone
+                                          ]).clone().getTracks());
+
+        var analyser = new AudioStreamAnalyser(ac, stream);
+        return 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);
+      }).then(() => {
+        info("Analysing audio output of clone()d tracks in original stream (1k) " +
+             "and clone()d tracks in stream clone (5k)");
+        var stream = new MediaStream([audioTrack1kClone, audioTrack5kClone]);
+        var streamClone = stream.clone();
+
+        stream.getTracks().forEach(t => stream.removeTrack(t));
+        stream.addTrack(streamClone.getTracks()[0]);
+        streamClone.removeTrack(streamClone.getTracks()[0]);
+
+        var analyser = new AudioStreamAnalyser(ac, stream);
+        return analyser.waitForAnalysisSuccess(array =>
+                 array[analyser.binIndexForFrequency(50)] < 50 &&
+                 array[analyser.binIndexForFrequency(1000)] > 200 &&
+                 array[analyser.binIndexForFrequency(3000)] < 50 &&
+                 array[analyser.binIndexForFrequency(5000)] < 50)
+          .then(() => {
+            var cloneAnalyser = new AudioStreamAnalyser(ac, streamClone);
+            return cloneAnalyser.waitForAnalysisSuccess(array =>
+                     array[cloneAnalyser.binIndexForFrequency(1000)] < 50 &&
+                     array[cloneAnalyser.binIndexForFrequency(3000)] < 50 &&
+                     array[cloneAnalyser.binIndexForFrequency(5000)] > 200 &&
+                     array[cloneAnalyser.binIndexForFrequency(10000)] < 50);
+          });
+      }).then(() => {
+        info("Analysing audio output enabled and disabled tracks that don't affect each other");
+        var stream = new MediaStream([audioTrack1kClone, audioTrack5kClone]);
+        var clone = stream.clone();
+
+        stream.getTracks()[0].enabled = true;
+        stream.getTracks()[1].enabled = false;
+
+        clone.getTracks()[0].enabled = false;
+        clone.getTracks()[1].enabled = true;
+
+        var analyser = new AudioStreamAnalyser(ac, stream);
+        return analyser.waitForAnalysisSuccess(array =>
+                 array[analyser.binIndexForFrequency(50)] < 50 &&
+                 array[analyser.binIndexForFrequency(1000)] > 200 &&
+                 array[analyser.binIndexForFrequency(3000)] < 50 &&
+                 array[analyser.binIndexForFrequency(5000)] < 50)
+          .then(() => {
+            var cloneAnalyser = new AudioStreamAnalyser(ac, clone);
+            return cloneAnalyser.waitForAnalysisSuccess(array =>
+                     array[cloneAnalyser.binIndexForFrequency(1000)] < 50 &&
+                     array[cloneAnalyser.binIndexForFrequency(3000)] < 50 &&
+                     array[cloneAnalyser.binIndexForFrequency(5000)] > 200 &&
+                     array[cloneAnalyser.binIndexForFrequency(10000)] < 50);
+          })
+          // Restore original tracks
+          .then(() => stream.getTracks().forEach(t => t.enabled = true));
+      });
+    }));
+</script>
+</pre>
+</body>
+</html>