Bug 1119593 - Dealing with multiple streams, r=drno
☠☠ backed out by a2984f1964f6 ☠ ☠
authorMartin Thomson <martin.thomson@gmail.com>
Tue, 27 Jan 2015 12:35:59 -0800
changeset 239513 db7c4ffd5a53d68c20d9b7235d845665ea145e1d
parent 239512 2832633668c3b3cf468061780afc98d00a87e934
child 239514 ae5cd730766250aac5ac66511364ecc178e87328
push id500
push userjoshua.m.grant@gmail.com
push dateThu, 29 Jan 2015 01:48:36 +0000
reviewersdrno
bugs1119593
milestone38.0a1
Bug 1119593 - Dealing with multiple streams, r=drno
dom/media/tests/mochitest/mediaStreamPlayback.js
dom/media/tests/mochitest/pc.js
dom/media/tests/mochitest/templates.js
--- a/dom/media/tests/mochitest/mediaStreamPlayback.js
+++ b/dom/media/tests/mochitest/mediaStreamPlayback.js
@@ -209,28 +209,28 @@ LocalMediaStreamPlayback.prototype = Obj
         setTimeout(() => {
           reject(new Error("ended event never fired"));
         }, ENDED_TIMEOUT_LENGTH);
       });
     }
   }
 });
 
+// haxx to prevent SimpleTest from failing at window.onload
+function addLoadEvent() {}
+
 var scriptsReady = Promise.all([
   "/tests/SimpleTest/SimpleTest.js",
   "head.js"
-].map(script => {
+].map(script  => {
   var el = document.createElement("script");
   el.src = script;
   document.head.appendChild(el);
   return new Promise(r => el.onload = r);
 }));
 
 function createHTML(options) {
   return scriptsReady.then(() => realCreateHTML(options));
 }
 
 function runTest(f) {
-  return scriptsReady.then(() => {
-    SimpleTest.waitForExplicitFinish();
-    return runTestWhenReady(f);
-  });
+  return scriptsReady.then(() => runTestWhenReady(f));
 }
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -735,17 +735,17 @@ function PeerConnectionWrapper(label, co
 
   this.constraints = [ ];
   this.offerOptions = {};
   this.streams = [ ];
   this.mediaCheckers = [ ];
 
   this.dataChannels = [ ];
 
-  this.onAddStreamFired = false;
+  this.addStreamCounter = {audio: 0, video: 0 };
 
   this._local_ice_candidates = [];
   this._remote_ice_candidates = [];
   this.holdIceCandidates = new Promise(r => this.releaseIceCandidates = r);
   this.localRequiresTrickleIce = false;
   this.remoteRequiresTrickleIce = false;
   this.localMediaElements = [];
 
@@ -772,27 +772,25 @@ function PeerConnectionWrapper(label, co
   /**
    * Callback for native peer connection 'onaddstream' events.
    *
    * @param {Object} event
    *        Event data which includes the stream to be added
    */
   this._pc.onaddstream = event => {
     info(this + ": 'onaddstream' event fired for " + JSON.stringify(event.stream));
-    // TODO: remove this once Bugs 998552 and 998546 are closed
-    this.onAddStreamFired = true;
 
     var type = '';
     if (event.stream.getAudioTracks().length > 0) {
       type = 'audio';
-      self.onAddStreamAudioCounter += event.stream.getAudioTracks().length;
+      this.addStreamCounter.audio += this.countTracksInStreams('audio', [event.stream]);
     }
     if (event.stream.getVideoTracks().length > 0) {
       type += 'video';
-      self.onAddStreamVideoCounter += event.stream.getVideoTracks().length;
+      this.addStreamCounter.video += this.countTracksInStreams('video', [event.stream]);
     }
     this.attachMedia(event.stream, type, 'remote');
   };
 
   createOneShotEventWrapper(this, this._pc, 'datachannel');
   this._pc.addEventListener('datachannel', e => {
     var wrapper = new DataChannelWrapper(e.channel, this);
     this.dataChannels.push(wrapper);
@@ -1218,27 +1216,21 @@ PeerConnectionWrapper.prototype = {
   },
 
   /**
    * Counts the amount of audio tracks in a given media constraint.
    *
    * @param constraints
    *        The contraint to be examined.
    */
-  countAudioTracksInMediaConstraint : function(constraints) {
-    if ((!constraints) || (constraints.length === 0)) {
+  countTracksInConstraint : function(type, constraints) {
+    if (!Array.isArray(constraints)) {
       return 0;
     }
-    var numAudioTracks = 0;
-    for (var i = 0; i < constraints.length; i++) {
-      if (constraints[i].audio) {
-        numAudioTracks++;
-      }
-    }
-    return numAudioTracks;
+    return constraints.reduce((sum, c) => sum + (c[type] ? 1 : 0), 0);
   },
 
   /**
    * Checks for audio in given offer options.
    *
    * @param options
    *        The options to be examined.
    */
@@ -1260,35 +1252,16 @@ PeerConnectionWrapper.prototype = {
     if (offerToReceiveAudio) {
       return 1;
     } else {
       return 0;
     }
   },
 
   /**
-   * Counts the amount of video tracks in a given media constraint.
-   *
-   * @param constraint
-   *        The contraint to be examined.
-   */
-  countVideoTracksInMediaConstraint : function(constraints) {
-    if ((!constraints) || (constraints.length === 0)) {
-      return 0;
-    }
-    var numVideoTracks = 0;
-    for (var i = 0; i < constraints.length; i++) {
-      if (constraints[i].video) {
-        numVideoTracks++;
-      }
-    }
-    return numVideoTracks;
-  },
-
-  /**
    * Checks for video in given offer options.
    *
    * @param options
    *        The options to be examined.
    */
   videoInOfferOptions : function(options) {
     if (!options) {
       return 0;
@@ -1307,102 +1280,97 @@ PeerConnectionWrapper.prototype = {
     if (offerToReceiveVideo) {
       return 1;
     } else {
       return 0;
     }
   },
 
   /*
-   * Counts the amount of audio tracks in a given set of streams.
+   * Counts the amount of tracks of the given type in a set of streams.
    *
+   * @param type audio|video
    * @param streams
    *        An array of streams (as returned by getLocalStreams()) to be
    *        examined.
    */
-  countAudioTracksInStreams : function(streams) {
-    if (!streams || (streams.length === 0)) {
+  countTracksInStreams: function(type, streams) {
+    if (!Array.isArray(streams)) {
       return 0;
     }
+    var f = (type === 'video') ? "getVideoTracks" : "getAudioTracks";
 
     return streams.reduce((count, st) => {
-      return count + st.getAudioTracks().length;
-    }, 0);
-  },
-
-  /*
-   * Counts the amount of video tracks in a given set of streams.
-   *
-   * @param streams
-   *        An array of streams (as returned by getLocalStreams()) to be
-   *        examined.
-   */
-  countVideoTracksInStreams: function(streams) {
-    if (!streams || (streams.length === 0)) {
-      return 0;
-    }
-
-    return streams.reduce((count, st) => {
-      return count + st.getVideoTracks().length;
+      return count + st[f]().length;
     }, 0);
   },
 
   /**
    * Checks that we are getting the media tracks we expect.
    *
-   * @param {object} constraintsRemote
-   *        The media constraints of the local and remote peer connection object
+   * @param {object} constraints
+   *        The media constraints of the remote peer connection object
    */
-  checkMediaTracks : function(constraintsRemote) {
-    var _checkMediaTracks = () => {
-      var localConstraintAudioTracks =
-        this.countAudioTracksInMediaConstraint(this.constraints);
-      var localStreams = this._pc.getLocalStreams();
-      var localAudioTracks = this.countAudioTracksInStreams(localStreams, false);
-      is(localAudioTracks, localConstraintAudioTracks, this + ' has ' +
-        localAudioTracks + ' local audio tracks');
-
-      var localConstraintVideoTracks =
-        this.countVideoTracksInMediaConstraint(this.constraints);
-      var localVideoTracks = this.countVideoTracksInStreams(localStreams, false);
-      is(localVideoTracks, localConstraintVideoTracks, this + ' has ' +
-        localVideoTracks + ' local video tracks');
+  checkMediaTracks : function(remoteConstraints) {
+    var waitForExpectedTracks = type => {
+      var outstandingCount = this.countTracksInConstraint(type, remoteConstraints);
+      outstandingCount -= this.addStreamCounter[type];
+      if (outstandingCount <= 0) {
+        return Promise.resolve();
+      }
 
-      var remoteConstraintAudioTracks =
-        this.countAudioTracksInMediaConstraint(constraintsRemote);
-      var remoteStreams = this._pc.getRemoteStreams();
-      var remoteAudioTracks = this.countAudioTracksInStreams(remoteStreams, false);
-      is(remoteAudioTracks, remoteConstraintAudioTracks, this + ' has ' +
-        remoteAudioTracks + ' remote audio tracks');
+      return new Promise(resolve => {
+        this._pc.addEventListener('addstream', e => {
+          outstandingCount -= this.countTracksInStreams(type, [e.stream]);
+          if (outstandingCount <= 0) {
+            resolve();
+          }
+        });
+      });
+    };
 
-      var remoteConstraintVideoTracks =
-        this.countVideoTracksInMediaConstraint(constraintsRemote);
-      var remoteVideoTracks = this.countVideoTracksInStreams(remoteStreams, false);
-      is(remoteVideoTracks, remoteConstraintVideoTracks, this + ' has ' +
-        remoteVideoTracks + ' remote video tracks');
-    }
-
-    // we have to do this check as the onaddstream never fires if the remote
-    // stream has no track at all!
-    var expectedRemoteTracks =
-      this.countAudioTracksInMediaConstraint(constraintsRemote) +
-      this.countVideoTracksInMediaConstraint(constraintsRemote);
-
-    // TODO: remove this once Bugs 998552 and 998546 are closed
-    if (this.onAddStreamFired || (expectedRemoteTracks == 0)) {
-      _checkMediaTracks();
-      return Promise.resolve();
-    }
+    var checkTrackCounts = (side, streams, constraints) => {
+      ['audio', 'video'].forEach(type => {
+        var actual = this.countTracksInStreams(type, streams);
+        var expected = this.countTracksInConstraint(type, constraints);
+        is(actual, expected, this + ' has ' + actual + ' ' +
+           side + ' ' + type + ' tracks');
+      });
+    };
 
     info(this + " checkMediaTracks() got called before onAddStream fired");
-    var checkPromise = new Promise(r => this._pc.addEventListener('addstream', r))
-      .then(_checkMediaTracks);
+    var checkPromise = Promise.all([
+      waitForExpectedTracks('audio'),
+      waitForExpectedTracks('video')
+    ]).then(() => {
+      checkTrackCounts('local', this._pc.getLocalStreams(), this.constraints);
+      checkTrackCounts('remote', this._pc.getRemoteStreams(), remoteConstraints);
+    });
     return timerGuard(checkPromise, 60000, "onaddstream never fired");
   },
 
+  checkMsids: function() {
+    var checkSdpForMsids = (desc, streams, side) => {
+      streams.forEach(stream => {
+        stream.getTracks().forEach(track => {
+          // TODO(bug 1089798): Once DOMMediaStream has an id field, we
+          // should be verifying that the SDP contains
+          // a=msid:<stream-id> <track-id>
+          ok(desc.sdp.match(new RegExp("a=msid:[^ ]+ " + track.id)),
+             side + " SDP contains track id " + track.id );
+        });
+      });
+    };
+
+    checkSdpForMsids(this.localDescription, this._pc.getLocalStreams(),
+                     "local");
+    checkSdpForMsids(this.remoteDescription, this._pc.getRemoteStreams(),
+                     "remote");
+   },
+
   verifySdp: function(desc, expectedType, offerConstraintsList, offerOptions, isLocal) {
     info("Examining this SessionDescription: " + JSON.stringify(desc));
     info("offerConstraintsList: " + JSON.stringify(offerConstraintsList));
     info("offerOptions: " + JSON.stringify(offerOptions));
     ok(desc, "SessionDescription is not null");
     is(desc.type, expectedType, "SessionDescription type is " + expectedType);
     ok(desc.sdp.length > 10, "SessionDescription body length is plausible");
     ok(desc.sdp.contains("a=ice-ufrag"), "ICE username is present in SDP");
@@ -1420,32 +1388,32 @@ PeerConnectionWrapper.prototype = {
       this.localRequiresTrickleIce = requiresTrickleIce;
     } else {
       this.remoteRequiresTrickleIce = requiresTrickleIce;
     }
 
     //TODO: how can we check for absence/presence of m=application?
 
     var audioTracks =
-      this.countAudioTracksInMediaConstraint(offerConstraintsList) ||
+        this.countTracksInConstraint('audio', offerConstraintsList) ||
       this.audioInOfferOptions(offerOptions);
 
     info("expected audio tracks: " + audioTracks);
     if (audioTracks == 0) {
       ok(!desc.sdp.contains("m=audio"), "audio m-line is absent from SDP");
     } else {
       ok(desc.sdp.contains("m=audio"), "audio m-line is present in SDP");
       ok(desc.sdp.contains("a=rtpmap:109 opus/48000/2"), "OPUS codec is present in SDP");
       //TODO: ideally the rtcp-mux should be for the m=audio, and not just
       //      anywhere in the SDP (JS SDP parser bug 1045429)
       ok(desc.sdp.contains("a=rtcp-mux"), "RTCP Mux is offered in SDP");
     }
 
     var videoTracks =
-      this.countVideoTracksInMediaConstraint(offerConstraintsList) ||
+        this.countTracksInConstraint('video', offerConstraintsList) ||
       this.videoInOfferOptions(offerOptions);
 
     info("expected video tracks: " + videoTracks);
     if (videoTracks == 0) {
       ok(!desc.sdp.contains("m=video"), "video m-line is absent from SDP");
     } else {
       ok(desc.sdp.contains("m=video"), "video m-line is present in SDP");
       if (this.h264) {
@@ -1666,21 +1634,21 @@ PeerConnectionWrapper.prototype = {
     });
     info("ICE connections according to stats: " + numIceConnections);
     if (answer.sdp.contains('a=group:BUNDLE')) {
       is(numIceConnections, 1, "stats reports exactly 1 ICE connection");
     } else {
       // This code assumes that no media sections have been rejected due to
       // codec mismatch or other unrecoverable negotiation failures.
       var numAudioTracks =
-        this.countAudioTracksInMediaConstraint(offerConstraintsList) ||
+          this.countTracksInConstraint('audio', offerConstraintsList) ||
         this.audioInOfferOptions(offerOptions);
 
       var numVideoTracks =
-        this.countVideoTracksInMediaConstraint(offerConstraintsList) ||
+          this.countTracksInConstraint('video', offerConstraintsList) ||
         this.videoInOfferOptions(offerOptions);
 
       var numDataTracks = this.dataChannels.length;
 
       var numAudioVideoDataTracks = numAudioTracks + numVideoTracks + numDataTracks;
       info("expected audio + video + data tracks: " + numAudioVideoDataTracks);
       is(numAudioVideoDataTracks, numIceConnections, "stats ICE connections matches expected A/V tracks");
     }
@@ -1728,38 +1696,37 @@ PeerConnectionWrapper.prototype = {
    *
    * @returns {String} The string representation
    */
   toString : function() {
     return "PeerConnectionWrapper (" + this.label + ")";
   }
 };
 
+// haxx to prevent SimpleTest from failing at window.onload
+function addLoadEvent() {}
+
 var scriptsReady = Promise.all([
   "/tests/SimpleTest/SimpleTest.js",
   "head.js",
   "templates.js",
   "turnConfig.js",
   "dataChannel.js",
   "network.js"
-].map(script => {
+].map(script  => {
   var el = document.createElement("script");
-  if (typeof scriptRelativePath === 'string' && script.charAt(0) !== "/") {
-    el.src = scriptRelativePath + script;
-  } else {
-    el.src = script;
+  if (typeof scriptRelativePath === 'string' && script.charAt(0) !== '/') {
+    script = scriptRelativePath + script;
   }
+  el.src = script;
   document.head.appendChild(el);
   return new Promise(r => { el.onload = r; el.onerror = r; });
 }));
 
 function createHTML(options) {
   return scriptsReady.then(() => realCreateHTML(options));
 }
 
 function runNetworkTest(testFunction) {
-  return scriptsReady.then(() => {
-    if (window.SimpleTest) {
-      SimpleTest.waitForExplicitFinish();
-    }
-    return startNetworkAndTest();
-  }).then(() => runTestWhenReady(testFunction));
+  return scriptsReady
+    .then(() => startNetworkAndTest())
+    .then(() => runTestWhenReady(testFunction));
 }
--- a/dom/media/tests/mochitest/templates.js
+++ b/dom/media/tests/mochitest/templates.js
@@ -399,17 +399,24 @@ var commandsPeerConnection = [
 
   function PC_LOCAL_CHECK_MEDIA_FLOW_PRESENT(test) {
     return test.pcLocal.checkMediaFlowPresent();
   },
 
   function PC_REMOTE_CHECK_MEDIA_FLOW_PRESENT(test) {
     return test.pcRemote.checkMediaFlowPresent();
   },
-
+/* TODO: re-enable when Bug 1095218 lands
+  function PC_LOCAL_CHECK_MSID(test) {
+    test.pcLocal.checkMsids();
+  },
+  function PC_REMOTE_CHECK_MSID(test) {
+    test.pcRemote.checkMsids();
+  },
+*/
   function PC_LOCAL_CHECK_STATS(test) {
     return test.pcLocal.getStats(null).then(stats => {
       test.pcLocal.checkStats(stats, test.steeplechase);
     });
   },
 
   function PC_REMOTE_CHECK_STATS(test) {
     test.pcRemote.getStats(null).then(stats => {