Bug 1355220 add RTCRtpSender/Receiver.getStats;r=jib,smaug
authorNico Grunbaum
Thu, 20 Apr 2017 17:54:20 -0700
changeset 409053 cc7b19b52df4b1c25d003d2e6deb2007f780cfa6
parent 409052 40d98a26598ec6e16e5f93cfd91e3dab28956e59
child 409054 04d8a87a833d8cd534bb4faa1ec2d45e32415bdb
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjib, smaug
bugs1355220
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1355220 add RTCRtpSender/Receiver.getStats;r=jib,smaug MozReview-Commit-ID: LZ4ItjFYxmk
dom/media/PeerConnection.js
dom/media/tests/mochitest/mochitest.ini
dom/media/tests/mochitest/pc.js
dom/media/tests/mochitest/test_peerConnection_sender_and_receiver_stats.html
dom/webidl/RTCRtpReceiver.webidl
dom/webidl/RTCRtpSender.webidl
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -1528,17 +1528,17 @@ class PeerConnectionObserver {
   onRemoveStream(stream) {
     this.dispatchEvent(new this._dompc._win.MediaStreamEvent("removestream",
                                                              { stream }));
   }
 
   onAddTrack(track, streams) {
     let pc = this._dompc;
     let receiver = pc._win.RTCRtpReceiver._create(pc._win,
-                                                  new RTCRtpReceiver(this,
+                                                  new RTCRtpReceiver(pc,
                                                                      track));
     pc._receivers.push(receiver);
     let ev = new pc._win.RTCTrackEvent("track", { receiver, track, streams });
     this.dispatchEvent(ev);
 
     // Fire legacy event as well for a little bit.
     ev = new pc._win.MediaStreamTrackEvent("addtrack", { track });
     this.dispatchEvent(ev);
@@ -1660,27 +1660,37 @@ class RTCRtpSender {
   setParameters(parameters) {
     return this._pc._win.Promise.resolve()
       .then(() => this._pc._setParameters(this, parameters));
   }
 
   getParameters() {
     return this._pc._getParameters(this);
   }
+
+  getStats() {
+    return this._pc._async(
+      async () => this._pc._getStats(this.track));
+  }
 }
 setupPrototype(RTCRtpSender, {
   classID: PC_SENDER_CID,
   contractID: PC_SENDER_CONTRACT,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
 });
 
 class RTCRtpReceiver {
   constructor(pc, track) {
     Object.assign(this, { _pc: pc, track });
   }
+
+  getStats() {
+    return this._pc._async(
+      async () => this._pc.getStats(this.track));
+  }
 }
 setupPrototype(RTCRtpReceiver, {
   classID: PC_RECEIVER_CID,
   contractID: PC_RECEIVER_CONTRACT,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
 });
 
 class CreateOfferRequest {
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -282,8 +282,10 @@ skip-if = (android_version == '18') # an
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_threeUnbundledConnections.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_selftest.html]
 # Bug 1227781: Crash with bogus TURN server.
 [test_peerConnection_bug1227781.html]
 [test_peerConnection_stats.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
+[test_peerConnection_sender_and_receiver_stats.html]
+skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -813,16 +813,33 @@ function PeerConnectionWrapper(label, co
     this.dataChannels.push(wrapper);
   });
 
   createOneShotEventWrapper(this, this._pc, 'signalingstatechange');
   createOneShotEventWrapper(this, this._pc, 'negotiationneeded');
 }
 
 PeerConnectionWrapper.prototype = {
+  /**
+   * Returns the senders
+   *
+   * @returns {sequence<RTCRtpSender>} the senders
+   */
+  getSenders: function() {
+    return this._pc.getSenders();
+  },
+
+  /**
+   * Returns the getters
+   *
+   * @returns {sequence<RTCRtpReceiver>} the receivers
+   */
+  getReceivers: function() {
+    return this._pc.getReceivers();
+  },
 
   /**
    * Returns the local description.
    *
    * @returns {object} The local description
    */
   get localDescription() {
     return this._pc.localDescription;
@@ -1476,16 +1493,57 @@ PeerConnectionWrapper.prototype = {
       Object.keys(this.expectedRemoteTrackInfoById)
           .map(id => this.remoteMediaElements
               .find(e => e.srcObject.getTracks().some(t => t.id == id)))
           .map(e => this.waitForMediaElementFlow(e)),
       this._pc.getSenders().map(sender => this.waitForRtpFlow(sender.track)),
       this._pc.getReceivers().map(receiver => this.waitForRtpFlow(receiver.track))));
   },
 
+  async waitForSyncedRtcp() {
+    // Ensures that RTCP is present
+    let ensureSyncedRtcp = async () => {
+      let report = await this._pc.getStats();
+      for (let [k, v] of report) {
+        if (v.type.endsWith("bound-rtp") && !v.remoteId) {
+          info(v.id + " is missing remoteId: " + JSON.stringify(v));
+          return null;
+        }
+        if (v.type == "inbound-rtp" && v.isRemote == true
+            && v.roundTripTime === undefined) {
+          info(v.id + " is missing roundTripTime: " + JSON.stringify(v));
+          return null;
+        }
+      }
+      return report;
+    }
+    let attempts = 0;
+    // Time-units are MS
+    const waitPeriod = 500;
+    const maxTime = 15000;
+    for (let totalTime = maxTime; totalTime > 0; totalTime -= waitPeriod) {
+      try {
+        let syncedStats = await ensureSyncedRtcp();
+        if (syncedStats) {
+          return syncedStats;
+        }
+      } catch (e) {
+          info(e);
+          info(e.stack);
+          throw e;
+      }
+      attempts += 1;
+      info("waitForSyncedRtcp: no synced RTCP on attempt" + attempts
+           + ", retrying.\n");
+      await wait(waitPeriod);
+    }
+    throw Error("Waiting for synced RTCP timed out after at least "
+                + maxTime + "ms");
+  },
+
   /**
    * Check that correct audio (typically a flat tone) is flowing to this
    * PeerConnection. Uses WebAudio AnalyserNodes to compare input and output
    * audio data in the frequency domain.
    *
    * @param {object} from
    *        A PeerConnectionWrapper whose audio RTPSender we use as source for
    *        the audio flow check.
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_sender_and_receiver_stats.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "1355220",
+    title: "RTCRtpSender.getStats() and RTCRtpReceiver.getStats()",
+    visible: true
+  });
+
+  var test;
+
+  var checkStats = (sndReport, rcvReport, mediaType) => {
+    // Returns SSRCs and checks that the tracks are of the correct mediaType
+    let getSsrcs = (report, kind) => {
+      return [...report.values()]
+        .filter(stat => stat.type.endsWith("bound-rtp")).map(stat =>{
+          is(stat.mediaType, kind, "mediaType of " + stat.id
+              + " is expected type " + kind);
+          return stat.ssrc;
+      }).sort().join("|");
+    };
+    let sndSsrcs = getSsrcs(sndReport, mediaType);
+    let rcvSsrcs = getSsrcs(rcvReport, mediaType);
+    ok(sndSsrcs, "sender SSRCs is not empty");
+    ok(rcvSsrcs, "receiver SSRCs is not empty");
+    is(sndSsrcs, rcvSsrcs, "sender SSRCs match receiver SSRCs");
+  };
+
+  // This MUST be run after PC_*_WAIT_FOR_MEDIA_FLOW to ensure that we have RTP
+  // before checking for RTCP.
+  // It will throw UnsyncedRtcpError if it times out waiting for sync.
+
+  var test;
+  runNetworkTest(function (options) {
+    test = new PeerConnectionTest(options);
+    test.chain.insertAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW",
+      async function PC_LOCAL_AND_REMOTE_CHECK_SENDER_RECEIVER_STATS(test) {
+        return Promise.all([test.pcLocal.waitForSyncedRtcp(),
+                  test.pcRemote.waitForSyncedRtcp()])
+          .then(async () => {
+            let senders = test.pcLocal.getSenders();
+            let receivers = test.pcRemote.getReceivers();
+            is(senders.length, 2, "Have exactly two senders.");
+            is(receivers.length, 2, "Have exactly two receivers.");
+            for(let kind of ["audio", "video"]) {
+              let senderStats =
+                  await senders.find(s => s.track.kind == kind).getStats();
+              is(senders.filter(s => s.track.kind == kind).length, 1,
+                  "Exactly 1 sender of kind " + kind);
+              let receiverStats =
+                  await receivers.find(r => r.track.kind == kind).getStats();
+              is(receivers.filter(r => r.track.kind == kind).length, 1,
+                  "Exactly 1 receiver of kind " + kind);
+
+              checkStats(senderStats, receiverStats, kind);
+            }
+          })
+        });
+    test.setMediaConstraints([{audio: true}, {video: true}], []);
+    test.run();
+  });
+
+</script>
+</pre>
+</body>
+</html>
\ No newline at end of file
--- a/dom/webidl/RTCRtpReceiver.webidl
+++ b/dom/webidl/RTCRtpReceiver.webidl
@@ -6,9 +6,10 @@
  * The origin of this IDL file is
  * http://lists.w3.org/Archives/Public/public-webrtc/2014May/0067.html
  */
 
 [Pref="media.peerconnection.enabled",
  JSImplementation="@mozilla.org/dom/rtpreceiver;1"]
 interface RTCRtpReceiver {
   readonly attribute MediaStreamTrack track;
+  Promise<RTCStatsReport> getStats();
 };
--- a/dom/webidl/RTCRtpSender.webidl
+++ b/dom/webidl/RTCRtpSender.webidl
@@ -68,11 +68,12 @@ dictionary RTCRtpParameters {
 
 [Pref="media.peerconnection.enabled",
  JSImplementation="@mozilla.org/dom/rtpsender;1"]
 interface RTCRtpSender {
   readonly attribute MediaStreamTrack track;
   Promise<void> setParameters (optional RTCRtpParameters parameters);
   RTCRtpParameters getParameters();
   Promise<void> replaceTrack(MediaStreamTrack track);
+  Promise<RTCStatsReport> getStats();
   [Pref="media.peerconnection.dtmf.enabled"]
   readonly attribute RTCDTMFSender? dtmf;
 };